3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Ionic Firebase Authenticationを使った、登録ユーザーのメールアドレスの変更方法

Last updated at Posted at 2019-11-05

#はじめに
・この記事はFirebase Authenticationを使った、登録ユーザーのメールアドレスの変更方法について書いています。

って訳で、Firebase Authenticationのメール認証を使っている人向けです。

・公式はこちら

・ionic + Firebaseのユーザー認証については、こちらを参考に。

・ionicのプロジェクトにFirebaseが入っている事を前提とします。

#概要
Firebase authenticationでは、メールアドレスの変更時に再認証が必要となる。

Firebase のユーザーを管理するより

アカウントの削除、メインのメールアドレスの設定、パスワードの変更といったセキュリティ上>重要な操作を行うには、ユーザーが最近ログインしている必要があります。ユーザーが最近ログ>インしていない場合、このような操作を行うと失敗し、エラーになります。

なので、以下ざっくりとした順番。

①設定ページにメールアドレス変更ボタン
②メールアドレス変更ボタンを押すとモーダルが開く
③現在のメールアドレスとパスワードを入力**(ここが再認証)**
④新しいメールアドレスを入力

こんな感じでいく。

#auth.serviceにメールアドレス再設定の処理を書く
まずはserviceにメールアドレス再設定の処理を書く。

パスワード再設定の場合と同じで、serviceを使う。

auth.service.ts
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();
  }

}

#設定ページにモーダルを開くボタンを設置

settings.page.html
<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-profilecomponentを作成
(componentとはページを構成する要素の部品のようなもの)

ionic g component settings/change-profile

スクリーンショット 2019-11-04 15.53.30.jpg

こんな感じになる。

モーダルとして開くにはもう少しやる事がある。

##親要素にモーダルのcomponentを登録する
settingsページから見て子要素となるChangeProfileComponentを、moduleページで登録。

settings.module.ts
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 {}

##ボタンを押してモーダルを開く

settings.page.ts
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と全く同じです。。。

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型のプロパティisCredentialfalseで用意。

ユーザーの再認証に成功すればisCredentialtrueとなり、ngIfでそれを感知して新しいパスワードの入力タブを出現させる。

これを実装すればOK!!!

まずはhtml

change-profile.component.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ファイル

change-profile.component.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.tsresetEmail()メソッドを注入。

#最後に
メールアドレスの変更ごとき簡単にできるだろうと思っていたが、再認証が必要って所で結構つまづいた。。。

再認証無しだとほぼ確実にauth/requires-recent-loginエラーを食う羽目になる。
そして公式ドキュメントがわかりにくい!!!
reauthenticateWithCredentialに必要なcredentialはどうやって作るんだよ!!って思った。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?