🌟

[Firebase Authentication]パスワード再設定時のメール本文とパスワード再設定画面をカスタマイズする

2024/04/25に公開

Firebase Authenticationでのパスワード再設定のフローは以下のようになります。

  1. メールアドレスを入力してパスワード再設定をリクエスト
  2. パスワード再設定画面へのリンクが含まれたメールが届くので開く
  3. パスワード再設定画面で新しいパスワードを設定

以下の要望があった時の対応方法を書こうと思います。

  • パスワード再設定画面へのリンクが含まれたメールの内容をFirebaeのデフォルトのものから変更したい
  • パスワード再設定画面のデザインを変更したい

メールのカスタマイズ

メールのカスタマイズは、Firebase上で行うことができます。

上記の画面で変更できます。

ユーザー登録時のメールアドレス検証の際に送られるメールは
スパム防止のためは編集できませんが、パスワード再設定メールはFirebase上で変更できるようです。

また、デフォルトではメールの送信はFirebaseによって行われますが、自前(他のメール送信サービスを使うなど)で送信することもできます。
その場合は、パスワード再設定用のパラメータが含まれたリンクをfirebaseライブラリのgeneratePasswordResetLink()によって生成し、そのリンクを本文に含める形で送ることができます。
参考: https://firebase.google.com/docs/auth/admin/email-action-links?hl=ja#generate_password_reset_email_link

パスワード再設定画面のカスタマイズ

アクションURLの変更

デフォルトでは、パスワード再設定メールに含まれるパスワード再設定画面へのリンク(以下アクションURL)はFirebaseで用意されている画面となっています。

アクションURLは以下のような形になっていて、https://fir-example-9902b.firebaseapp.com/__/auth/action
このような画面になります。

Firebaseで用意されている画面をカスタマイズすることはできません。
そのため、アクションURLの遷移先をこちら側で用意した画面にすることで、パスワード再設定画面のカスタマイズを可能にします。

アクションURLはFirebase上で変更することができます。

(今回はローカル環境で試しているためlocalhostを指定しています)
アクションURLが先程設定したURLに諸々パラメータが付いたものに変わっています

Firebase側で用意されている画面を使用する場合は、パスワード再設定の処理はFirebase側でよしなにやってくれるのでこちらが実装する必要はありませんでした。
しかし、画面をこちらで用意した場合はパスワード再設定の処理を実装する必要が出てきます。

パスワード再設定処理の実装

URLに含まれている次のパラメータを使用します。

  • mode
    • 完了するユーザー管理操作。次のいずれかの値です。
      • resetPassword
      • recoverEmail
      • verifyEmail
  • oobCode
    • リクエストを識別し、検証するためのワンタイム コード

参考: https://firebase.google.com/docs/auth/custom-email-handler?hl=ja#web-modular-api_1

簡単のため今回はmoderesetPasswordの時だけを考えます。

app/auth/action/password-reset-form.tsx
"use client";
import { initializeApp } from "firebase/app";
import {
  confirmPasswordReset,
  getAuth,
  verifyPasswordResetCode,
} from "firebase/auth";
import { FormEvent, useEffect } from "react";

const config = {
  apiKey: "AIzexample",
};
const app = initializeApp(config);
const auth = getAuth(app);

export const PasswordResetForm = ({ oobCode }: { oobCode: string }) => {
  const resetPassword = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const password = formData.get("password") as string;
    confirmPasswordReset(auth, oobCode, password)
      .then(() => {
        console.log("パスワードを再設定成功");
      })
      .catch((error) => {
        // 各種エラーの場合に
        console.error("パスワードを再設定失敗", error);
      });
  };

  useEffect(() => {
    // oobCodeを検証することもできる
    // 検証に成功するとメールアドレスが取得できる
    verifyPasswordResetCode(auth, oobCode)
      .then((email) => {
        console.log("email", email);
      })
      .catch((error) => {
        console.error("oobCodeの検証失敗", error);
      });
  }, [oobCode]);

  return (
    <form onSubmit={resetPassword}>
      <label htmlFor="email">新しいパスワード: </label>
      <input className="border" type="password" name="password" id="password" />
      <div>
        <button
          type="submit"
          className="text-white rounded-md bg-indigo-600 px-3 py-2"
        >
          設定
        </button>
      </div>
    </form>
  );
};

これでパスワード再設定を行いつつ、画面をカスタマイズできるようになりました。

コードをもう少し見ていきます。

confirmPasswordReset(auth, oobCode, password)
      .then(() => {
        console.log("パスワードを再設定成功");
      })
      .catch((error) => {
        // 各種エラーの場合に
        console.error("パスワードを再設定失敗", error);
      });

参考: https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#confirmpasswordreset

firebaseライブラリのconfirmPasswordReset()を実行することでパスワードの変更がFirebaseに反映されます。
confirmPasswordReset()は以下の5種類のエラーを判別することができます。

  1. auth/expired-action-code
  2. auth/invalid-action-code
  3. auth/user-disabled
  4. auth/user-not-found
  5. auth/weak-password

実際のプロダクトで使う時は、上記のエラーが出たときにどのような要件・仕様にすべきか検討し、適切に処理してあげる必要があるかと思います。

また、auth/invalid-action-codeに関しては、

  1. すでにそのコードが使用済みでパスワード再設定が済んでいる
  2. 適当な文字などの不正なコード

のときに出て、どちらかは区別できません。
例えば、このエラーの時は、パスワード再設定完了済みもしくは不正なURLであることをユーザーに伝えたり、2のパターンは切り捨ててパスワード再設定完了済みであることだけを案内したり、プロダクトによっていくつがパターンが考えられるかと思います。

事前にoobCodeの検証をすることもできます

verifyPasswordResetCode(auth, oobCode)
      .then((email) => {
        console.log("email", email);
      })
      .catch((error) => {
        console.error("oobCodeの検証失敗", error);
      });

confirmPasswordReset()をする前に、oobCodeが有効か検証することもできます。
参考: https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#verifypasswordresetcode

これにより、例えば有効期限が切れていたらユーザーがパスワードを入力する前にエラーを通知して、ユーザーに無駄な作業をさせなくて済ますことができたりします。

おわりに

以上となります。この記事が少しでも参考になれば幸いです。
いいねを押してもらえれば励みになりますのでよろしくお願いします🙏

Discussion