添加 TOTP 多重身份验证(Web 应用)

如果您已升级到 Firebase Authentication with Identity Platform,可以向应用添加基于时间的动态密码 (TOTP) 多重身份验证 (MFA)。

借助 Firebase Authentication with Identity Platform,您可以将 TOTP 用作 MFA 的其他因素。启用此功能后,尝试登录您的应用的用户会看到系统要求提供 TOTP。要生成 TOTP,用户必须使用可生成有效 TOTP 代码的身份验证器应用,例如 Google 身份验证器

准备工作

  1. 请至少启用一个支持 MFA 的提供方。请注意,除以下提供方以外的所有提供方都支持 MFA:

    • 电话身份验证
    • 匿名身份验证
    • 自定义身份验证令牌
    • Apple 游戏中心
  2. 确保您的应用会验证用户的电子邮件地址。MFA 要求验证电子邮件地址。这可防止图谋不轨者使用别人的电子邮件地址注册服务,然后通过添加第二重身份验证因素来阻止电子邮件地址的实际所有者。

  3. 安装 Firebase JavaScript SDK(如果您尚未安装)。

    只有模块化 Web SDK v9.19.1 及更高版本支持 TOTP MFA。

  4. 安装 Firebase Admin SDK(如果您尚未安装)。

    只有 Firebase Admin SDK 11.6.0 及更高版本支持 TOTP MFA。

启用 TOTP MFA

如需启用 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。

选择注册模式

您可以选择应用是否要求多重身份验证,以及何时和如何注册用户。一些常见模式包括:

  • 在注册过程中注册用户的第二重身份验证。如果应用要求所有用户进行多重身份验证,请使用此方法。

  • 提供可在注册期间跳过第二重身份验证注册的选项。如果您希望在应用中鼓励但不要求多重身份验证,则可以使用此方法。

  • 提供从用户的账号或个人资料管理页面(而不是注册界面)添加第二重身份验证的功能。这样可以使注册过程更顺畅,同时仍可为注重安全的用户提供多重身份验证。

  • 如果用户希望访问安全性要求更高的功能,再要求添加第二重身份验证。

在 TOTP MFA 中注册用户

启用 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 的二维码,快速添加新的 TOTP 密钥。如需为此目的生成二维码,请使用 generateQrCodeUrl() 生成 URI,然后使用您选择的二维码库对其进行编码。例如:

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

    无论您是否显示二维码,都应始终显示密钥以支持无法读取二维码的身份验证器应用:

    // 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);
    

让用户通过第二重身份验证登录

如需让用户使用 TOTP MFA 登录,请使用以下代码:

  1. 导入所需的 MFA 类和函数:

    import {
        getAuth,
        getMultiFactorResolver,
        TotpMultiFactorGenerator,
    } from "firebase/auth";
    
  2. 调用其中一个 signInWith- 方法,就像未使用 MFA 一样。(例如 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 流程应首先提示用户选择要使用的第二重身份验证。您可以通过检查 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
        );
    }
}

后续步骤