#はじめに
・この記事はFirebase Authenticationを使った、登録ユーザーのメールアドレスの変更方法について書いています。
って訳で、Firebase Authenticationのメール認証を使っている人向けです。
・公式はこちら
・ionic + Firebaseのユーザー認証については、こちらを参考に。
・ionicのプロジェクトにFirebaseが入っている事を前提とします。
#概要
Firebase authenticationでは、メールアドレスの変更時に再認証が必要となる。
アカウントの削除、メインのメールアドレスの設定、パスワードの変更といったセキュリティ上>重要な操作を行うには、ユーザーが最近ログインしている必要があります。ユーザーが最近ログ>インしていない場合、このような操作を行うと失敗し、エラーになります。
なので、以下ざっくりとした順番。
①設定ページにメールアドレス変更ボタン
②メールアドレス変更ボタンを押すとモーダルが開く
③現在のメールアドレスとパスワードを入力**(ここが再認証)**
④新しいメールアドレスを入力
こんな感じでいく。
#auth.serviceにメールアドレス再設定の処理を書く
まずはserviceにメールアドレス再設定の処理を書く。
パスワード再設定の場合と同じで、serviceを使う。
import { AngularFireAuth } from '@angular/fire/auth';
import { AlertController } from '@ionic/angular';
export interface User {
uid: string;
email: string;
displayName?: string;
myCustomData?: string;
photoURL?: string;
}
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(
private afAuth: AngularFireAuth,
private alertCtrl: AlertController,
) { }
resetEmail(email: string) {
const user = this.afAuth.auth.currentUser;
return user
.updateEmail(email)
.then(() => {
this.newEmailAlert(email);
})
.catch(error => {
console.log(error);
const code = error.code;
const header = 'メールアドレの変更に失敗';
let message = 'エラーが発生しました。もう一度お試しください。。。';
if (code === 'auth/requires-recent-login') {
message =
'もう一度ログインする必要があります。ログアウトして再度ログインしてからお試しください。';
} else if (code === 'auth/email-already-in-use') {
message = 'このメールアドレスは既に登録されています。。。';
}
this.showAlert(header, message);
});
}
// メールアドレス変更完了のアラート
async newEmailAlert(email: string) {
const alert = await this.alertCtrl.create({
header: 'メールアドレスを変更しました',
message: `次回からログインするときは${email}のメールアドレスを使ってください。`,
buttons: ['OK']
});
await alert.present();
}
}
#設定ページにモーダルを開くボタンを設置
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-back-button defaultHref="/home"></ion-back-button>
</ion-buttons>
<ion-title>設定</ion-title>
</ion-toolbar>
</ion-header>
<ion-content *ngIf="currentUser" padding>
<ion-grid>
<ion-row>
<ion-col size="12" size-sm="8" offset-sm="2">
<ion-card>
<ion-card-header text-center>
<ion-card-title>アカウント情報</ion-card-title>
<ion-card-subtitle>メールアドレスの変更ができます</ion-card-subtitle>
</ion-card-header>
<ion-card-content text-center>
<div>
<h2 class="ion-margin-bottom">現在のメールアドレス</h2>
<p>
<ion-icon name="mail"></ion-icon>
{{ currentUser.email }}
</p>
</div>
<div>
<!-- このボタンでモーダルが開く -->
<ion-button
class="ion-margin-bottom ion-margin-top"
expand="full"
fill="solid"
color="tertiary"
(click)="onChangeProfile()"
>
<ion-icon name="create" slot="start"></ion-icon>
アカウント情報を変更する</ion-button>
</div>
</ion-card-content>
</ion-card>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
##モーダルのcomponentを作成
settingsページのフォルダ内に、change-profile
componentを作成
(componentとはページを構成する要素の部品のようなもの)
ionic g component settings/change-profile
こんな感じになる。
モーダルとして開くにはもう少しやる事がある。
##親要素にモーダルのcomponentを登録する
settingsページ
から見て子要素となるChangeProfileComponent
を、moduleページで登録。
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { SettingsPage } from './settings.page';
import { ChangeProfileComponent } from './change-profile/change-profile.component';
const routes: Routes = [
{
path: '',
component: SettingsPage
}
];
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
RouterModule.forChild(routes)
],
// declarationsとentryComponentsに追加
declarations: [SettingsPage, ChangeProfileComponent],
entryComponents: [ChangeProfileComponent]
})
export class SettingsPageModule {}
##ボタンを押してモーダルを開く
import { ChangeProfileComponent } from './change-profile/change-profile.component';
import { ModalController } from '@ionic/angular';
import { AngularFireAuth } from '@angular/fire/auth';
import { User } from '../auth/auth.service';
@Component({
selector: 'app-settings',
templateUrl: './settings.page.html',
styleUrls: ['./settings.page.scss']
})
export class SettingsPage implements OnInit {
private currentUser: User;
constructor(
private afAuth: AngularFireAuth,
private modalCtrl: ModalController
) {}
ngOnInit() {
this.afAuth.auth.onAuthStateChanged((user) => {
if (user != null) {
this.currentUser = user;
} else {
this.router.navigateByUrl('/auth');
}
});
}
onChangeProfile() {
this.modalCtrl
.create({
component: ChangeProfileComponent,
// 現在ログインしているユーザーの情報currentUserとしてを渡す
componentProps: { currentUser: this.currentUser }
})
.then(modalEl => {
modalEl.present();
});
}
}
1つ目のsettings.page.html
と全く同じです。。。
<!-- このボタンでモーダルが開く -->
<ion-button
class="ion-margin-bottom ion-margin-top"
expand="full"
fill="solid"
color="tertiary"
(click)="onChangeProfile()"
>
<ion-icon name="create" slot="start"></ion-icon>
アカウント情報を変更する</ion-button>
これでとりあえず空っぽのモーダルは開いた
#モーダルで開いたChangeProfileComponentを作る
このページでは
③現在のメールアドレスとパスワードを入力**(ここが再認証)**
と
④新しいメールアドレスを入力
を実装する。
もし③の再認証に成功すれば、④の新しいメールアドレスを入力するタブが出現するようにしたい。
どうすれば同じChangeProfileComponent
ページ内で、上記の事を実現できるか?
これには**AngularのngIf
**を使う事でこれは行けそうだ。
##再認証のtrue or falseを確かめる
まずboolean型のプロパティisCredential
をfalseで用意。
ユーザーの再認証に成功すればisCredential
がtrueとなり、ngIf
でそれを感知して新しいパスワードの入力タブを出現させる。
これを実装すればOK!!!
まずはhtml
<ion-header>
<ion-toolbar color="primary">
<ion-title>アカウント情報の変更</ion-title>
<ion-buttons slot="primary">
<ion-button (click)="onCancel()">
<ion-icon name="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content *ngIf="currentUser" padding>
<ion-grid>
<!-- このion-rowは再認証されていない状態で存在する -->
<ion-row *ngIf="!isCredential">
<ion-col size="12" size-sm="8" offset-sm="2">
<ion-item class="loginHeader" lines="none" text-center>
<h2>再認証の為にログインが必要です。</h2>
</ion-item>
<form #f="ngForm">
<ion-item class="formItem">
<ion-label position="floating">
<ion-icon name="mail"></ion-icon>
現在のメールアドレス
</ion-label>
<ion-input disabled="true" email [(ngModel)]="currentUser.email" type="email" name="email" #emailCtrl="ngModel">
</ion-input>
</ion-item>
<ion-item>
<ion-label position="floating">
<ion-icon name="key" padding-right></ion-icon>パスワード
</ion-label>
<ion-input required [(ngModel)]="password" type="password" name="password" #passwordCtrl="ngModel"
minlength="6">
</ion-input>
</ion-item>
<ion-item *ngIf="!passwordCtrl.valid && passwordCtrl.touched" lines="none">
<ion-label color="danger">パスワードは6文字以上</ion-label>
</ion-item>
<ion-button [disabled]="!f.form.valid" (click)="makeCredential()" seize="large" expand="block">
ログイン
</ion-button>
</form>
</ion-col>
</ion-row>
<!-- このion-rowが再認証された後に出現 -->
<ion-row *ngIf="isCredential">
<ion-col size="12" size-sm="8" offset-sm="2">
<form #f="ngForm">
<ion-item>
<ion-label position="floating">
<ion-icon name="mail"></ion-icon>
新しいメールアドレス
</ion-label>
<ion-input required email type="email" [(ngModel)]="newEmail" name="email" #emailCtrl="ngModel">
</ion-input>
</ion-item>
<ion-item *ngIf="!emailCtrl.valid && emailCtrl.touched" lines="none">
<ion-label color="danger">メールアドレスを入力してね!</ion-label>
</ion-item>
<ion-button [disabled]="!f.form.valid" (click)="updateProfile()" seize="large" expand="block">
メールアドレスを変更する
</ion-button>
</form>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
続いてtsファイル
import { AuthService } from './../../auth/auth.service';
import { Component, OnInit } from '@angular/core';
import { ModalController, AlertController } from '@ionic/angular';
import { User } from 'src/app/auth/auth.service';
import { AngularFireAuth } from '@angular/fire/auth';
import { Router } from '@angular/router';
import * as firebase from 'firebase/app';
import { HomeService } from 'src/app/home/home.service';
@Component({
selector: 'app-change-profile',
templateUrl: './change-profile.component.html',
styleUrls: ['./change-profile.component.scss']
})
export class ChangeProfileComponent implements OnInit {
currentUser: User;
// 初期設定でfalseとしておく
isCredential = false;
password: '';
newEmail: '';
constructor(
private modalCtrl: ModalController,
private authService: AuthService,
private afAuth: AngularFireAuth,
private router: Router,
private alertCtrl: AlertController,
private homeService: HomeService,
) {}
ngOnInit() {
this.afAuth.auth.onAuthStateChanged((user) => {
if (user != null) {
this.currentUser = user;
} else {
this.router.navigateByUrl('/auth');
}
});
}
onCancel() {
this.modalCtrl.dismiss();
}
makeCredential() {
const user = this.afAuth.auth.currentUser;
// EmailAuthProviderを使うので、ユーザーはパスワードだけ入力
const credential = firebase.auth.EmailAuthProvider.credential(
user.email,
this.password
);
user.reauthenticateWithCredential(credential)
.then(() => {
// もし再認証が成功すればisCredentialがtrueとなる
this.isCredential = true;
}).catch(error => {
console.log(error);
const code = error.code;
const header = 'ログイン失敗';
let message = 'エラーが発生しました。もう一度お試しください。。。';
if (code === 'auth/wrong-password') {
message =
'パスワードが違います。。。';
}
this.showAlert(header, message);
});
}
private showAlert(header: string, message: string) {
this.alertCtrl
.create({
header: header,
message: message,
buttons: ['OK']
})
.then(alertEl => alertEl.present());
}
updateProfile() {
this.authService.resetEmail(this.newEmail);
}
}
updateProfile()
には、一番はじめに書いたauth.service.ts
のresetEmail()
メソッドを注入。
#最後に
メールアドレスの変更ごとき簡単にできるだろうと思っていたが、再認証が必要って所で結構つまづいた。。。
再認証無しだとほぼ確実にauth/requires-recent-login
エラーを食う羽目になる。
そして公式ドキュメントがわかりにくい!!!
reauthenticateWithCredential
に必要なcredentialはどうやって作るんだよ!!って思った。