🙆‍♀️

Firebase Authで登録,サインインやemail検証のカスタマイズをする場合の実装

2023/08/02に公開

Firebaseを使ってユーザー登録する時に、フロントから直接Firebaseとやりとりするとアプリケーション側のDBのに反映させるタイミングがないなと思ったのでどうやるんだろうと思って調べたものを書いています。

Firebase側のプロジェクトの用意は済んだところからです。
Next.jsのコンポーネントで試しています。

登録・サインイン

import { FirebaseOptions, initializeApp } from "firebase/app";
import {
  createUserWithEmailAndPassword,
  getAuth,
  signInWithEmailAndPassword,
} from "firebase/auth";
import { api } from "~/utils/api";

export default function Home() {
  const firebaseConfig: FirebaseOptions = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
  };
  initializeApp(firebaseConfig);

  const auth = getAuth();
  const email = process.env.NEXT_PUBLIC_EXAMPLE_EMAIL || "";
  const password = process.env.NEXT_PUBLIC_EXAMPLE_PASSWORD || "";

  const handleSignUp = async () => {
    createUserWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        const user = userCredential.user;
        console.log("user", user);
      })
      .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        console.log("errorCode", errorCode);
        console.log("errorMessage", errorMessage);
      });
  };

  const handleSignIn = async () => {
    const userCredential = await signInWithEmailAndPassword(
      auth,
      email,
      password
    );
    const user = userCredential.user;
    console.log("user.emailVerified", user.emailVerified);
  };

  return (
    <>
      <div>
        <button onClick={handleSignUp}>Sign up</button>
      </div>
      <div>
        <button onClick={handleSignIn}>Sign in</button>
      </div>
    </>
  );
}

登録

createUserWithEmailAndPassword()でemail, passwordを使って登録。
https://firebase.google.com/docs/auth/web/start?hl=ja

パスワードの強度

Firebase側でチェックしてるのは文字数が6文字以上であるかだけ。
https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseAuthWeakPasswordException

登録時のエラー

https://firebase.google.com/docs/auth/admin/errors?hl=ja

例えば、同一emailで登録済みの時

errorCode auth/email-already-exists
errorMessage Firebase: Error (auth/email-already-in-use).

サインイン

signInWithEmailAndPassword()
https://firebase.google.com/docs/auth/web/password-auth?hl=ja

成功時のレスポンス

https://firebase.google.com/docs/reference/js/auth.usercredential.md?hl=ja#usercredential_interface

userとしては
https://firebase.google.com/docs/reference/js/auth.user.md#user_interface
https://firebase.google.com/docs/reference/js/auth.userinfo.md#userinfo_interface

失敗時のレスポンス

https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#signinwithemailandpassword

例えば、パスワードが違う時

errorCode auth/wrong-password
errorMessage Firebase: Error (auth/wrong-password).

email検証

https://firebase.google.com/docs/reference/js/auth.md#sendemailverification

以下のように、userを引数にsendEmailVerification()を使ってfirebaseからemailを送信できる。

  const handleSignUp = async () => {
    createUserWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        const user = userCredential.user;
        sendEmailVerification(user);
      })
  };

メールに含まれるリンクにアクセスすることで、email検証が完了する。
リンクを踏んだ後の画面は以下。

email検証が完了しているかどうかはemailVerifiedで分かる。

const user = userCredential.user;
console.log("user.emailVerified", user.emailVerified);

email検証カスタマイズ

検証emailの文面は変更できない。
リンク踏んだ後の画面も変更はできない。

このあたりをカスマイズするには、

  • 検証URLの生成
  • メールは独自で送信
    する必要がある。

検証URLのBaseとなるURLはFirebaseコンソールで変更することができます。
Firebaseコンソール -> Templates -> メールアドレスの確認 -> 編集 -> アクションURL


ローカル環境で動作を試したいので、アクションURLをhttp://localhost:3000に変更しました。

トークンを含めたemail検証リンクを生成するには、firebase-admingenerateEmailVerificationLink()を使います。
https://firebase.google.com/docs/auth/admin/email-action-links?hl=ja#generate_email_verification_link

import * as admin from "firebase-admin";
...
if (!admin.apps[0]) {
        await admin.initializeApp({
          credential: admin.credential.cert({
            projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
            privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, "\n"),
            clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
          }),
        });
      }
      const authAdmin = admin.auth();
      const email = process.env.NEXT_PUBLIC_EXAMPLE_EMAIL || "";
      const link = await authAdmin.generateEmailVerificationLink(email);
      console.log("link", link);
...

ローカル環境でのfirebase-adminの初期化はfirebaseの初期化と違って、サービスアカウントの情報を使います。
https://firebase.google.com/docs/admin/setup?hl=ja

generateEmailVerificationLink()した結果以下のように検証URLが生成されます。

link http://localhost:3000?mode=verifyEmail&oobCode=LB1upROfTggbM1M4YA3bbAzK0SaA62PkmC1ZnsUP4jIAAAGJrsW5oA&apiKey=AIzaSyDwUd_-eVxPObVudrEdBW7bM_2G6AiDrL4&lang=en

これを含めて独自でemail送信を行えばemail検証フローをカスタマイズできます。

上記URLへアクセスしたあとの検証方法は以下となります。
https://firebase.google.com/docs/auth/custom-email-handler?hl=ja#web-modular-api_1

applyActionCode()によってactionCodeを検証します。

import { FirebaseOptions, initializeApp } from "firebase/app";
import {
  createUserWithEmailAndPassword,
  getAuth,
  signInWithEmailAndPassword,
  checkActionCode,
} from "firebase/auth";
...

export default function Home() {
  const firebaseConfig: FirebaseOptions = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
  };
  initializeApp(firebaseConfig);
  
  const auth = getAuth();
  
  const searchParams = useSearchParams();
  const actionCode = searchParams.get("oobCode");
  
  useEffect(() => {
    if (actionCode) {
	applyActionCode(auth, actionCode)
      });
    }
  }, [actionCode]);
...

applyActionCode(auth, actionCode)を呼ぶとfirebase側のuser情報がemail検証済みの状態に変わります。

終わりに

emailの編集やパスワードリセットなどもactionCodeを使って実装できます。

Discussion