ウェブアプリにTOTP多要素認証を追加する

Identity Platform を使用する Firebase Authentication にアップグレードした場合は、アプリに時間ベースのワンタイム パスワード(TOTP)多要素認証(MFA)を追加できます。

Identity Platform を使用する Firebase Authentication では、MFAの追加要素として TOTP を使用できます。この機能を有効にした後、ユーザーがアプリにログインしようとすると、TOTP のリクエストが表示されます。有効な TOTP コードを生成するには、Google 認証システムなど、それを生成できる認証システムアプリを使用する必要があります。

準備

  1. MFA をサポートするプロバイダを少なくとも 1 つ有効にします。以下を除くすべてのプロバイダが MFA をサポートしています

    • 電話認証
    • 匿名認証
    • カスタム認証トークン
    • Apple Game Center
  2. アプリでユーザーのメールアドレスが検証されるようにします。MFA では、メールの確認を行う必要があります。これにより、悪意のある人物が所有していないメールアドレスでサービスに登録してから第 2 要素を追加することで、そのメールアドレスの実際の所有者をロックアウトすることを防ぎます。

  3. Firebase JavaScript SDK をまだインストールしていない場合は、インストールします。

    TOTP MFA は、バージョン v9.19.1 以降のモジュラー Web SDK でのみサポートされます。

  4. Firebase Admin SDK をまだインストールしていない場合は、インストールします。

    TOTP MFA は、Firebase Admin SDK バージョン 11.6.0 以降でのみサポートされています。

TOTP MFA を有効にする

第 2 要素として TOTP を有効にするには、Admin SDK を使用するか、プロジェクト構成 REST エンドポイントを呼び出します。

Admin SDK を使用するには、次のコマンドを実行します。

import { getAuth } from 'firebase-admin/auth';

getAuth().projectConfigManager().updateProjectConfig(
{
      multiFactorConfig: {
          providerConfigs: [{
              state: "ENABLED",
              totpProviderConfig: {
                  adjacentIntervals: {
                      NUM_ADJ_INTERVALS
                  },
              }
          }]
      }
})

以下を置き換えます。

  • NUM_ADJ_INTERVALS: 時間枠間隔の数(0 ~ 10) デフォルトは 5 です。

REST API を使用して TOTP MFA を有効にするには、次のコマンドを実行します。

curl -X PATCH "https://identitytoolkit.googleapis.com/admin/v2/projects/PROJECT_ID/config?updateMask=mfa" \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    -H "Content-Type: application/json" \
    -H "X-Goog-User-Project: PROJECT_ID" \
    -d \
    '{
        "mfa": {
          "providerConfigs": [{
            "state": "ENABLED",
            "totpProviderConfig": {
              "adjacentIntervals": "NUM_ADJ_INTERVALS"
            }
          }]
       }
    }'

以下を置き換えます。

  • PROJECT_ID: プロジェクト ID。
  • NUM_ADJ_INTERVALS: 時間枠間隔の数(0 ~ 10) デフォルトは 5 です。

登録パターンを選択する

アプリで多要素認証が必要かどうかと、ユーザーを登録する方法とタイミングを選択できます。一般的なパターンには、次のようなものがあります。

  • 登録の一部として、ユーザーの第 2 要素を登録する。アプリがすべてのユーザーに対して多要素認証を必要とする場合は、この方法を使用します。

  • 登録中に第 2 要素の登録をスキップできる選択肢を用意する。アプリで多要素認証を必須とはしないが、推奨したい場合は、この方法を使用できます。

  • 登録画面ではなく、ユーザーのアカウントまたはプロフィールの管理ページから第 2 要素を追加する機能を用意する。これにより、登録プロセス中の摩擦が最小限に抑えられる一方、セキュリティに敏感なユーザーは多要素認証を利用できるようになります。

  • セキュリティ要件が強化された機能にユーザーがアクセスする際には、第 2 要素を段階的に追加することを要求する。

TOTP MFA にユーザーを登録する

アプリの第 2 要素として TOTP MFA を有効にしたら、TOTP MFA にユーザーを登録するクライアント側ロジックを実装します。

  1. 必要な MFA クラスと関数をインポートします。

    import {
      multiFactor,
      TotpMultiFactorGenerator,
      TotpSecret,
      getAuth,
    } from "firebase/auth";
    
  2. ユーザーを再認証します。

  3. 認証されたユーザーの TOTP シークレットを生成します。

    // Generate a TOTP secret.
    const multiFactorSession = await multiFactor(currentUser).getSession();
    const totpSecret = await TotpMultiFactorGenerator.generateSecret(
      multiFactorSession
    );
    
  4. ユーザーにシークレットを表示し、認証アプリに入力するようユーザーに求めます。

    多くの認証システムアプリでは、Google 認証システムと互換性のあるキー URI を表す QR コードをスキャンすることで、新しい TOTP シークレットをすばやく追加できます。この目的の QR コードを生成するには、generateQrCodeUrl() で URI を生成し、任意の QR コード ライブラリを使用してエンコードします。次に例を示します。

    const totpUri = totpSecret.generateQrCodeUrl(
        currentUser.email,
        "Your App's Name"
    );
    await QRExampleLib.toCanvas(totpUri, qrElement);
    

    QR コードを表示するかどうかに関係なく、QR コードを読み取ることができない認証システムアプリをサポートするために、常にシークレットキーを表示します。

    // Also display this key:
    const secret = totpSecret.secretKey;
    

    ユーザーが認証システム アプリにシークレットを追加すると、TOTP の生成が開始されます。

  5. 認証システム アプリに表示された TOTP を入力し、それを使用して MFA 登録を確定するようユーザーに促します。

    // Ask the user for a verification code from the authenticator app.
    const verificationCode = // Code from user input.
    
    // Finalize the enrollment.
    const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment(
      totpSecret,
      verificationCode
    );
    await multiFactor(currentUser).enroll(multiFactorAssertion, mfaDisplayName);
    

第 2 要素でのユーザーのログイン

TOTP MFA でユーザーをログインさせるには、次のコードを使用します。

  1. 必要な MFA クラスと関数をインポートします。

    import {
        getAuth,
        getMultiFactorResolver,
        TotpMultiFactorGenerator,
    } from "firebase/auth";
    
  2. MFA を使用していない場合と同様に、signInWith メソッドのいずれかを呼び出します。(例: signInWithEmailAndPassword())。このメソッドから auth/multi-factor-auth-required エラーがスローされた場合は、アプリの MFA フローを開始します。

    try {
        const userCredential = await signInWithEmailAndPassword(
            getAuth(),
            email,
            password
        );
        // If the user is not enrolled with a second factor and provided valid
        // credentials, sign-in succeeds.
    
        // (If your app requires MFA, this could be considered an error
        // condition, which you would resolve by forcing the user to enroll a
        // second factor.)
    
        // ...
    } catch (error) {
        switch (error.code) {
            case "auth/multi-factor-auth-required":
                // Initiate your second factor sign-in flow. (See next step.)
                // ...
                break;
            case ...:  // Handle other errors, such as wrong passwords.
                break;
        }
    }
    
  3. アプリの MFA フローでは、まず、使用する第 2 要素を選択するようユーザーに促します。サポートされている第 2 要素のリストを取得するには、MultiFactorResolver インスタンスの hints プロパティを調べます。

    const mfaResolver = getMultiFactorResolver(getAuth(), error);
    const enrolledFactors = mfaResolver.hints.map(info => info.displayName);
    
  4. ユーザーが TOTP の使用を選択した場合は、認証システム アプリに表示された TOTP を入力し、それを使用してログインするようにユーザーに求めます。

    switch (mfaResolver.hints[selectedIndex].factorId) {
        case TotpMultiFactorGenerator.FACTOR_ID:
            const otpFromAuthenticator = // OTP typed by the user.
            const multiFactorAssertion =
                TotpMultiFactorGenerator.assertionForSignIn(
                    mfaResolver.hints[selectedIndex].uid,
                    otpFromAuthenticator
                );
            try {
                const userCredential = await mfaResolver.resolveSignIn(
                    multiFactorAssertion
                );
                // Successfully signed in!
            } catch (error) {
                // Invalid or expired OTP.
            }
            break;
        case PhoneMultiFactorGenerator.FACTOR_ID:
            // Handle SMS second factor.
            break;
        default:
            // Unsupported second factor?
            break;
    }
    

TOTP MFA の登録を解除する

このセクションでは、ユーザーによる TOTP MFA の登録の解除を処理する方法について説明します。

ユーザーが複数の MFA オプションに登録し、最後に有効にしたオプションから登録を解除した場合は、ユーザーはauth/user-token-expired を受け取ってログアウトされます。ユーザーはもう一度ログインして、既存の認証情報(メールアドレスとパスワードなど)を確認する必要があります。

ユーザーの登録を解除してエラーを処理し、再認証をトリガーするには、次のコードを使用します。

import {
    EmailAuthProvider,
    TotpMultiFactorGenerator,
    getAuth,
    multiFactor,
    reauthenticateWithCredential,
} from "firebase/auth";

try {
    // Unenroll from TOTP MFA.
    await multiFactor(currentUser).unenroll(TotpMultiFactorGenerator.FACTOR_ID);
} catch  (error) {
    if (error.code === 'auth/user-token-expired') {
        // If the user was signed out, re-authenticate them.

        // For example, if they signed in with a password, prompt them to
        // provide it again, then call `reauthenticateWithCredential()` as shown
        // below.

        const credential = EmailAuthProvider.credential(email, password);
        await reauthenticateWithCredential(
            currentUser,
            credential
        );
    }
}

次のステップ