欢迎参加我们将于 2022 年 10 月 18 日举办的 Firebase 峰会(线上线下同时进行),了解 Firebase 如何帮助您加快应用开发速度、满怀信心地发布应用,并在之后根据需要轻松地扩大应用规模。立即报名

将多重身份验证添加到您的 Web 应用程序

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

如果您已使用 Identity Platform 升级到 Firebase 身份验证,则可以将 SMS 多重身份验证添加到您的 Web 应用程序。

多因素身份验证可提高应用程序的安全性。虽然攻击者经常破坏密码和社交帐户,但拦截短信更加困难。

在你开始之前

  1. 启用至少一个支持多重身份验证的提供程序。电话验证、匿名验证和 Apple Game Center 外,每个提供商都支持 MFA。

  2. 确保您的应用正在验证用户电子邮件。 MFA 需要电子邮件验证。这可以防止恶意行为者使用他们不拥有的电子邮件注册服务,然后通过添加第二个因素锁定真正的所有者。

使用多租户

如果要在多租户环境中启用多重身份验证,请确保完成以下步骤(除了本文档中的其余说明之外):

  1. 在 GCP Console 中,选择您要使用的租户。

  2. 在您的代码中,将Auth实例上的tenantId字段设置为您的租户 ID。例如:

    Web version 9

    import { getAuth } from "firebase/auth";
    
    const auth = getAuth(app);
    auth.tenantId = "myTenantId1";
    

    Web version 8

    firebase.auth().tenantId = 'myTenantId1';
    

启用多重身份验证

  1. 打开 Firebase 控制台的身份验证 > 登录方法页面。

  2. 高级部分,启用SMS Multi-factor Authentication

    您还应该输入用于测试应用程序的电话号码。虽然可选,但强烈建议注册测试电话号码以避免在开发过程中受到限制。

  3. 如果您尚未授权应用的域,请将其添加到 Firebase 控制台的“身份验证”>“设置”页面上的允许列表中。

选择注册模式

您可以选择您的应用程序是否需要多重身份验证,以及注册用户的方式和时间。一些常见的模式包括:

  • 注册用户的第二个因素作为注册的一部分。如果您的应用需要对所有用户进行多重身份验证,请使用此方法。

  • 提供可跳过的选项以在注册期间注册第二个因素。想要鼓励但不要求多因素身份验证的应用程序可能更喜欢这种方法。

  • 提供从用户帐户或配置文件管理页面添加第二个因素的能力,而不是注册屏幕。这最大限度地减少了注册过程中的摩擦,同时仍然为安全敏感的用户提供多因素身份验证。

  • 当用户想要访问具有更高安全要求的功能时,需要逐步添加第二个因素。

设置 reCAPTCHA 验证器

在发送 SMS 代码之前,您需要配置 reCAPTCHA 验证器。 Firebase 使用 reCAPTCHA 通过确保电话号码验证请求来自您应用的允许域之一来防止滥用。

您无需手动设置 reCAPTCHA 客户端;客户端 SDK 的RecaptchaVerifier对象自动创建和初始化任何必要的客户端密钥和秘密。

使用不可见的 reCAPTCHA

RecaptchaVerifier对象支持不可见的 reCAPTCHA ,它通常可以在不需要任何交互的情况下验证用户。要使用不可见的 reCAPTCHA,请创建一个size参数设置为invisibleRecaptchaVerifier ,并指定启动多因素注册的 UI 元素的 ID:

Web version 9

import { RecaptchaVerifier } from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier("sign-in-button", {
    "size": "invisible",
    "callback": function(response) {
        // reCAPTCHA solved, you can proceed with
        // phoneAuthProvider.verifyPhoneNumber(...).
        onSolvedRecaptcha();
    }
}, auth);

Web version 8

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
'size': 'invisible',
'callback': function(response) {
  // reCAPTCHA solved, you can proceed with phoneAuthProvider.verifyPhoneNumber(...).
  onSolvedRecaptcha();
}
});

使用 reCAPTCHA 小部件

要使用可见的 reCAPTCHA 小部件,请创建一个 HTML 元素来包含该小部件,然后使用 UI 容器的 ID 创建一个RecaptchaVerifier对象。您还可以选择设置当 reCAPTCHA 解决或过期时调用的回调:

Web version 9

import { RecaptchaVerifier } from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier(
    "recaptcha-container",

    // Optional reCAPTCHA parameters.
    {
      "size": "normal",
      "callback": function(response) {
        // reCAPTCHA solved, you can proceed with 
        // phoneAuthProvider.verifyPhoneNumber(...).
        onSolvedRecaptcha();
      },
      "expired-callback": function() {
        // Response expired. Ask user to solve reCAPTCHA again.
        // ...
      }
    }, auth
);

Web version 8

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
  'recaptcha-container',
  // Optional reCAPTCHA parameters.
  {
    'size': 'normal',
    'callback': function(response) {
      // reCAPTCHA solved, you can proceed with phoneAuthProvider.verifyPhoneNumber(...).
      // ...
      onSolvedRecaptcha();
    },
    'expired-callback': function() {
      // Response expired. Ask user to solve reCAPTCHA again.
      // ...
    }
  });

预渲染 reCAPTCHA

或者,您可以在开始双因素注册之前预呈现 reCAPTCHA:

Web version 9

recaptchaVerifier.render()
    .then(function (widgetId) {
        window.recaptchaWidgetId = widgetId;
    });

Web version 8

recaptchaVerifier.render()
  .then(function(widgetId) {
    window.recaptchaWidgetId = widgetId;
  });

render()解析后,您将获得 reCAPTCHA 的小部件 ID,您可以使用它来调用reCAPTCHA API

var recaptchaResponse = grecaptcha.getResponse(window.recaptchaWidgetId);

注册第二个因素

为用户注册一个新的次要因素:

  1. 重新验证用户。

  2. 要求用户输入他们的电话号码。

  3. 如上一节所述,初始化 reCAPTCHA 验证程序。如果已配置 RecaptchaVerifier 实例,请跳过此步骤:

    Web version 9

    import { RecaptchaVerifier } from "firebase/auth";
    
    const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
    

    Web version 8

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. 为用户获取多因素会话:

    Web version 9

    import { multiFactor } from "firebase/auth";
    
    multiFactor(user).getSession().then(function (multiFactorSession) {
        // ...
    });
    

    Web version 8

    user.multiFactor.getSession().then(function(multiFactorSession) {
      // ...
    })
    
  5. 使用用户的电话号码和多因素会话初始化PhoneInfoOptions对象:

    Web version 9

    // Specify the phone number and pass the MFA session.
    const phoneInfoOptions = {
      phoneNumber: phoneNumber,
      session: multiFactorSession
    };
    

    Web version 8

    // Specify the phone number and pass the MFA session.
    var phoneInfoOptions = {
      phoneNumber: phoneNumber,
      session: multiFactorSession
    };
    
  6. 向用户手机发送验证信息:

    Web version 9

    import { PhoneAuthProvider } from "firebase/auth";
    
    const phoneAuthProvider = new PhoneAuthProvider(auth);
    phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
        .then(function (verificationId) {
            // verificationId will be needed to complete enrollment.
        });
    

    Web version 8

    var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
    // Send SMS verification code.
    return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
      .then(function(verificationId) {
        // verificationId will be needed for enrollment completion.
      })
    

    虽然不是必需的,但最好提前通知用户他们将收到 SMS 消息,并且适用标准费率。

  7. 如果请求失败,请重置 reCAPTCHA,然后重复上一步,以便用户重试。请注意, verifyPhoneNumber()将在抛出错误时自动重置 reCAPTCHA,因为 reCAPTCHA 令牌只能一次性使用。

    Web version 9

    grecaptcha.reset(window.recaptchaWidgetId);
    
    // Or, if you haven't stored the widget ID:
    recaptchaVerifier.render()
      .then(function(widgetId) {
        grecaptcha.reset(widgetId);
      });
    

    Web version 8

    grecaptcha.reset(window.recaptchaWidgetId);
    
    // Or, if you haven't stored the widget ID:
    recaptchaVerifier.render()
      .then(function(widgetId) {
        grecaptcha.reset(widgetId);
      });
    
  8. 发送 SMS 代码后,要求用户验证代码:

    Web version 9

    // Ask user for the verification code. Then:
    const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
    

    Web version 8

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  9. 使用PhoneAuthCredential初始化MultiFactorAssertion对象:

    Web version 9

    import { PhoneMultiFactorGenerator } from "firebase/auth";
    
    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    Web version 8

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  10. 完成报名。或者,您可以指定第二个因素的显示名称。这对于具有多个第二因素的用户很有用,因为电话号码在身份验证流程中被屏蔽(例如,+1******1234)。

    Web version 9

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    multiFactor(user).enroll(multiFactorAssertion, "My personal phone number");
    

    Web version 8

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user.multiFactor.enroll(multiFactorAssertion, 'My personal phone number');
    

下面的代码显示了注册第二个因素的完整示例:

Web version 9

import {
    multiFactor, PhoneAuthProvider, PhoneMultiFactorGenerator,
    RecaptchaVerifier
} from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
multiFactor(user).getSession()
    .then(function (multiFactorSession) {
        // Specify the phone number and pass the MFA session.
        const phoneInfoOptions = {
            phoneNumber: phoneNumber,
            session: multiFactorSession
        };

        const phoneAuthProvider = new PhoneAuthProvider(auth);

        // Send SMS verification code.
        return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier);
    }).then(function (verificationId) {
        // Ask user for the verification code. Then:
        const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
        const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);

        // Complete enrollment.
        return multiFactor(user).enroll(multiFactorAssertion, mfaDisplayName);
    });

Web version 8

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
user.multiFactor.getSession().then(function(multiFactorSession) {
  // Specify the phone number and pass the MFA session.
  var phoneInfoOptions = {
    phoneNumber: phoneNumber,
    session: multiFactorSession
  };
  var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
  // Send SMS verification code.
  return phoneAuthProvider.verifyPhoneNumber(
      phoneInfoOptions, recaptchaVerifier);
})
.then(function(verificationId) {
  // Ask user for the verification code.
  var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
  var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
  // Complete enrollment.
  return user.multiFactor.enroll(multiFactorAssertion, mfaDisplayName);
});

恭喜!您成功为用户注册了第二个身份验证因素。

使用第二个因素让用户登录

要使用双因素 SMS 验证登录用户:

  1. 使用他们的第一个因素登录用户,然后捕获auth/multi-factor-auth-required错误。此错误包含一个解析器、关于已注册的第二个因素的提示,以及一个证明用户成功通过第一个因素进行身份验证的基础会话。

    例如,如果用户的第一个因素是电子邮件和密码:

    Web version 9

    import { getAuth, getMultiFactorResolver} from "firebase/auth";
    
    const auth = getAuth();
    signInWithEmailAndPassword(auth, email, password)
        .then(function (userCredential) {
            // User successfully signed in and is not enrolled with a second factor.
        })
        .catch(function (error) {
            if (error.code == 'auth/multi-factor-auth-required') {
                // The user is a multi-factor user. Second factor challenge is required.
                resolver = getMultiFactorResolver(auth, error);
                // ...
            } else if (error.code == 'auth/wrong-password') {
                // Handle other errors such as wrong password.
            }
    });
    

    Web version 8

    firebase.auth().signInWithEmailAndPassword(email, password)
      .then(function(userCredential) {
        // User successfully signed in and is not enrolled with a second factor.
      })
      .catch(function(error) {
        if (error.code == 'auth/multi-factor-auth-required') {
          // The user is a multi-factor user. Second factor challenge is required.
          resolver = error.resolver;
          // ...
        } else if (error.code == 'auth/wrong-password') {
          // Handle other errors such as wrong password.
        } ...
      });
    

    如果用户的第一个因素是联合提供程序,例如 OAuth、SAML 或 OIDC,请在调用signInWithPopup()signInWithRedirect()后捕获错误。

  2. 如果用户注册了多个次要因素,请询问他们使用哪一个:

    Web version 9

    // Ask user which second factor to use.
    // You can get the masked phone number via resolver.hints[selectedIndex].phoneNumber
    // You can get the display name via resolver.hints[selectedIndex].displayName
    
    if (resolver.hints[selectedIndex].factorId === PhoneMultiFactorGenerator.FACTOR_ID) {
        // User selected a phone second factor.
        // ...
    } else {
        // Unsupported second factor.
        // Note that only phone second factors are currently supported.
    }
    

    Web version 8

    // Ask user which second factor to use.
    // You can get the masked phone number via resolver.hints[selectedIndex].phoneNumber
    // You can get the display name via resolver.hints[selectedIndex].displayName
    if (resolver.hints[selectedIndex].factorId === firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID) {
      // User selected a phone second factor.
      // ...
    } else {
      // Unsupported second factor.
      // Note that only phone second factors are currently supported.
    }
    
  3. 如上一节所述,初始化 reCAPTCHA 验证程序。如果已配置 RecaptchaVerifier 实例,请跳过此步骤:

    Web version 9

    import { RecaptchaVerifier } from "firebase/auth";
    
    recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
    

    Web version 8

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. 使用用户的电话号码和多因素会话初始化PhoneInfoOptions对象。这些值包含在传递给auth/multi-factor-auth-required错误的resolver对象中:

    Web version 9

    const phoneInfoOptions = {
        multiFactorHint: resolver.hints[selectedIndex],
        session: resolver.session
    };
    

    Web version 8

    var phoneInfoOptions = {
      multiFactorHint: resolver.hints[selectedIndex],
      session: resolver.session
    };
    
  5. 向用户手机发送验证信息:

    Web version 9

    // Send SMS verification code.
    const phoneAuthProvider = new PhoneAuthProvider(auth);
    phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
        .then(function (verificationId) {
            // verificationId will be needed for sign-in completion.
        });
    

    Web version 8

    var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
    // Send SMS verification code.
    return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
      .then(function(verificationId) {
        // verificationId will be needed for sign-in completion.
      })
    
  6. 如果请求失败,请重置 reCAPTCHA,然后重复上一步,以便用户可以重试:

    Web version 9

    grecaptcha.reset(window.recaptchaWidgetId);
    
    // Or, if you haven't stored the widget ID:
    recaptchaVerifier.render()
      .then(function(widgetId) {
        grecaptcha.reset(widgetId);
      });
    

    Web version 8

    grecaptcha.reset(window.recaptchaWidgetId);
    
    // Or, if you haven't stored the widget ID:
    recaptchaVerifier.render()
      .then(function(widgetId) {
        grecaptcha.reset(widgetId);
      });
    
  7. 发送 SMS 代码后,要求用户验证代码:

    Web version 9

    const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
    

    Web version 8

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  8. 使用PhoneAuthCredential初始化MultiFactorAssertion对象:

    Web version 9

    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    Web version 8

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  9. 调用resolver.resolveSignIn()完成二次认证。然后,您可以访问原始登录结果,其中包括标准的提供商特定数据和身份验证凭据:

    Web version 9

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(multiFactorAssertion)
        .then(function (userCredential) {
            // userCredential will also contain the user, additionalUserInfo, optional
            // credential (null for email/password) associated with the first factor sign-in.
    
            // For example, if the user signed in with Google as a first factor,
            // userCredential.additionalUserInfo will contain data related to Google 
            // provider that the user signed in with.
            // - user.credential contains the Google OAuth credential.
            // - user.credential.accessToken contains the Google OAuth access token.
            // - user.credential.idToken contains the Google OAuth ID token.
        });
    

    Web version 8

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(multiFactorAssertion)
      .then(function(userCredential) {
        // userCredential will also contain the user, additionalUserInfo, optional
        // credential (null for email/password) associated with the first factor sign-in.
        // For example, if the user signed in with Google as a first factor,
        // userCredential.additionalUserInfo will contain data related to Google provider that
        // the user signed in with.
        // user.credential contains the Google OAuth credential.
        // user.credential.accessToken contains the Google OAuth access token.
        // user.credential.idToken contains the Google OAuth ID token.
      });
    

下面的代码显示了一个多因素用户登录的完整示例:

Web version 9

import {
    getAuth,
    getMultiFactorResolver,
    PhoneAuthProvider,
    PhoneMultiFactorGenerator,
    RecaptchaVerifier,
    signInWithEmailAndPassword
} from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);

const auth = getAuth();
signInWithEmailAndPassword(auth, email, password)
    .then(function (userCredential) {
        // User is not enrolled with a second factor and is successfully
        // signed in.
        // ...
    })
    .catch(function (error) {
        if (error.code == 'auth/multi-factor-auth-required') {
            const resolver = getMultiFactorResolver(auth, error);
            // Ask user which second factor to use.
            if (resolver.hints[selectedIndex].factorId ===
                PhoneMultiFactorGenerator.FACTOR_ID) {
                const phoneInfoOptions = {
                    multiFactorHint: resolver.hints[selectedIndex],
                    session: resolver.session
                };
                const phoneAuthProvider = new PhoneAuthProvider(auth);
                // Send SMS verification code
                return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
                    .then(function (verificationId) {
                        // Ask user for the SMS verification code. Then:
                        const cred = PhoneAuthProvider.credential(
                            verificationId, verificationCode);
                        const multiFactorAssertion =
                            PhoneMultiFactorGenerator.assertion(cred);
                        // Complete sign-in.
                        return resolver.resolveSignIn(multiFactorAssertion)
                    })
                    .then(function (userCredential) {
                        // User successfully signed in with the second factor phone number.
                    });
            } else {
                // Unsupported second factor.
            }
        } else if (error.code == 'auth/wrong-password') {
            // Handle other errors such as wrong password.
        }
    });

Web version 8

var resolver;
firebase.auth().signInWithEmailAndPassword(email, password)
  .then(function(userCredential) {
    // User is not enrolled with a second factor and is successfully signed in.
    // ...
  })
  .catch(function(error) {
    if (error.code == 'auth/multi-factor-auth-required') {
      resolver = error.resolver;
      // Ask user which second factor to use.
      if (resolver.hints[selectedIndex].factorId ===
          firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID) {
        var phoneInfoOptions = {
          multiFactorHint: resolver.hints[selectedIndex],
          session: resolver.session
        };
        var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
        // Send SMS verification code
        return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
          .then(function(verificationId) {
            // Ask user for the SMS verification code.
            var cred = firebase.auth.PhoneAuthProvider.credential(
                verificationId, verificationCode);
            var multiFactorAssertion =
                firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
            // Complete sign-in.
            return resolver.resolveSignIn(multiFactorAssertion)
          })
          .then(function(userCredential) {
            // User successfully signed in with the second factor phone number.
          });
      } else {
        // Unsupported second factor.
      }
    } else if (error.code == 'auth/wrong-password') {
      // Handle other errors such as wrong password.
    } ...
  });

恭喜!您使用多重身份验证成功登录了用户。

下一步是什么