使用 JavaScript 通过电话号码进行 Firebase 身份验证

使用 Firebase 身份验证,您可以通过向用户的电话发送短信来协助用户登录。用户使用短信中包含的一次性验证码登录。

要向应用中添加电话号码登录方式,最简单的方法就是使用 FirebaseUI。它包括一个普适性登录微件,可为电话号码登录方式、基于密码的登录方式和联合登录方式实现登录流程。本文档介绍了如何使用 Firebase SDK 实现电话号码登录流程。

准备工作

如果您尚未将初始化代码段从 Firebase 控制台复制到您的项目(如将 Firebase 添加至您的 JavaScript 项目中所述),请先执行此操作。

安全问题

与其他可用的方法相比,仅使用电话号码进行身份验证的方式虽然便捷,但安全性较低,因为同一电话号码很容易流转给不同用户使用。此外,在具有多份用户个人资料的设备上,任何一位可以接收短信的用户都能使用该设备的电话号码登录帐号。

如果您在应用中使用电话号码登录机制,则应同时提供更安全的登录方法,并将使用电话号码登录的安全隐患告知用户。

为您的 Firebase 项目启用电话号码登录

要让用户能够通过短信登录,您必须先为 Firebase 项目启用电话号码登录功能,操作步骤如下:

  1. Firebase 控制台中,打开 Authentication(身份验证)部分。
  2. 登录方法页面上,启用电话号码登录方法。
  3. 在同一页面上,如果 OAuth 重定向网域部分未列出将托管您应用的网域,请添加您的网域。

Firebase 的电话号码登录请求配额非常充足,大多数应用都不会遇到配额问题。但是,如果您需要通过电话号码身份验证让数量非常多的用户登录,则可能需要升级您的定价方案。请参阅定价页面。

设置 reCAPTCHA 验证程序

在让用户使用其电话号码登录之前,您必须设置 Firebase 的 reCAPTCHA 验证程序。Firebase 使用 reCAPTCHA 来防止滥用行为,具体方法包括确保电话号码验证请求来自您应用允许的一个网域。

您无需手动设置 reCAPTCHA 客户端;在使用 Firebase SDK 的 RecaptchaVerifier 对象时,Firebase 会自动创建和处理任何必要的客户端加密密钥和解密密钥。

RecaptchaVerifier 对象支持不可见的 reCAPTCHA(通常无需任何用户操作便能验证用户身份)以及 reCAPTCHA 微件(始终需要用户互动才能成功完成验证)。

在底层呈现的 reCAPTCHA 可以根据用户的偏好设置进行本地化,方法是在呈现 reCAPTCHA 之前更新身份验证实例中的语言代码。发送给用户的短信(其中包含验证码)也会相应地进行本地化。

firebase.auth().languageCode = 'it';
// To apply the default browser preference instead of explicitly setting it.
// firebase.auth().useDeviceLanguage();

使用不可见的 reCAPTCHA

要使用不可见的 reCAPTCHA,请创建一个 RecaptchaVerifier 对象并将其 size 参数设置为 invisible,同时指定用于提交登录表单的按钮的 ID。例如:

window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
  'size': 'invisible',
  'callback': function(response) {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    onSignInSubmit();
  }
});

使用 reCAPTCHA 微件

要使用可见的 reCAPTCHA 微件,请在页面上创建一个包含此微件的元素,然后再创建一个 RecaptchaVerifier 对象,同时指定该容器的 ID。例如:

window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container');

可选:指定 reCAPTCHA 参数

您可以选择为 RecaptchaVerifier 对象设置回调函数,以便在用户解决 reCAPTCHA 或 reCAPTCHA 在用户提交表单前到期的情况下调用:

window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', {
  'size': 'normal',
  'callback': function(response) {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    // ...
  },
  'expired-callback': function() {
    // Response expired. Ask user to solve reCAPTCHA again.
    // ...
  }
});

可选:预呈现 reCAPTCHA

如果您要在提交登录请求之前预呈现 reCAPTCHA,请调用 render

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

render 解析完成后,您会获得 reCAPTCHA 的微件 ID,此 ID 可用于调用 reCAPTCHA API:

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

向用户的电话发送验证码

要启动电话号码登录流程,请向用户显示一个要求其提供电话号码的界面,然后调用 signInWithPhoneNumber 以请求 Firebase 通过短信向用户电话发送身份验证码:

  1. 获取用户的电话号码。

    虽然相关的法律要求可能不尽相同,但为了避免用户不满,最佳做法是告知用户,如果他们选择使用电话登录,则可能会收到一条验证短信,并需按标准费率支付短信费用。

  2. 调用 signInWithPhoneNumber,向其传递用户的电话号码和您之前创建的 RecaptchaVerifier
    var phoneNumber = getPhoneNumberFromUserInput();
    var appVerifier = window.recaptchaVerifier;
    firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
        .then(function (confirmationResult) {
          // SMS sent. Prompt user to type the code from the message, then sign the
          // user in with confirmationResult.confirm(code).
          window.confirmationResult = confirmationResult;
        }).catch(function (error) {
          // Error; SMS not sent
          // ...
        });
    
    如果 signInWithPhoneNumber 导致错误,请重置 reCAPTCHA,以便用户重试:
    grecaptcha.reset(window.recaptchaWidgetId);
    
    // Or, if you haven't stored the widget ID:
    window.recaptchaVerifier.render().then(function(widgetId) {
      grecaptcha.reset(widgetId);
    }
    

signInWithPhoneNumber 方法会向用户发起 reCAPTCHA 挑战,如果用户通过这项挑战,则会请求 Firebase 身份验证向用户电话发送一条含有验证码的短信。

使用验证码让用户登录

signInWithPhoneNumber 调用成功之后,提示用户输入自己通过短信收到的验证码。然后,将此验证码传递给 ConfirmationResult 对象的 confirm 方法(该方法已被传递给 signInWithPhoneNumber 的履行状况处理程序,即它的 then 块),使用户登录到帐号。例如:

var code = getCodeFromUserInput();
confirmationResult.confirm(code).then(function (result) {
  // User signed in successfully.
  var user = result.user;
  // ...
}).catch(function (error) {
  // User couldn't sign in (bad verification code?)
  // ...
});

如果 confirm 调用成功,则表示用户已成功登录。

获取中间 AuthCredential 对象

如果您需要获取用户帐号的 AuthCredential 对象,请将确认结果返回的验证 ID 和相应验证码传递给 PhoneAuthProvider.credential,而不是调用 confirm

var credential = firebase.auth.PhoneAuthProvider.credential(confirmationResult.verificationId, code);

然后,您可以使用该凭据进行用户登录:

firebase.auth().signInAndRetrieveDataWithCredential(credential);

使用已列入白名单的电话号码进行测试

您可以通过 Firebase 控制台将电话号码列入白名单以用于开发。将电话号码列入白名单有以下好处:

  • 可测试电话号码身份验证,而不占用使用量配额。
  • 可测试电话号码身份验证,而无需实际发送短信。
  • 可使用同一电话号码运行连续测试,而不会受到短信发送数量的限制。如果审核人员恰好使用相同的电话号码进行测试,这样做会使在 App Store 审核过程中遭拒的风险降至最低。
  • 无需其他操作即可在开发环境中轻松进行测试,例如可以在 iOS 模拟器或没有 Google Play 服务的 Android 模拟器中进行开发。
  • 可编写集成测试,而不会被通常在生产环境中应用于真实电话号码的安全检查所屏蔽。

要列入白名单的电话号码必须符合以下要求:

  1. 确保您使用的是不存在的虚构号码。Firebase 身份验证不允许您将真实用户使用的现有电话号码列入白名单。您可以使用以 555 为前缀的数字作为美国测试电话号码,例如:+1 650-555-3434
  2. 电话号码必须采用正确的格式,以符合长度要求和其他限制。这些电话号码仍将与真实用户的电话号码一样经过相同的验证。
  3. 您最多可以添加 10 个电话号码用于开发。
  4. 使用难以猜到的测试电话号码/验证码,并经常更换。

将电话号码和验证码列入白名单

  1. Firebase 控制台中,打开 Authentication(身份验证)部分。
  2. 登录方法标签中,启用电话登录提供方(如果您尚未启用)。
  3. 打开用于测试的电话号码折叠菜单。
  4. 提供您想要用于测试的电话号码,例如 +1 650-555-3434。
  5. 为该特定号码提供 6 位验证码,例如 654321。
  6. 添加该号码。如有需要,您可以删除电话号码及其验证码,只需将鼠标悬停在相应的行上并点击垃圾桶图标即可。

手动测试

您可以直接在应用中开始使用已列入白名单的电话号码。这样一来,您就可以在开发阶段执行手动测试,而不会遇到配额问题或受到限制。您也可以直接通过 iOS 模拟器或没有 Google Play 服务的 Android 模拟器进行测试。

当您提供已列入白名单的电话号码并发送验证码时,系统实际上不会发送短信。相反,您需要提供之前配置的验证码才能完成登录。

完成登录后,系统会使用该电话号码创建一位 Firebase 用户。该用户的行为和属性与真实电话号码用户相同,并且可通过同样的方式使用实时数据库/Cloud Firestore 和其他服务。在此过程中生成的 ID 令牌与真实电话号码用户的令牌具有相同的签名。

您还可以利用自定义声明为此类用户设置测试角色,以将其作为虚构用户区分开来(如果您想进一步限制其访问权限)。

集成测试

除了手动测试外,Firebase 身份验证还提供了 API 来帮助您编写用于进行电话身份验证测试的集成测试。这些 API 通过停用 reCAPTCHA 要求(在网页应用中)和静默推送通知(在 iOS 应用中)来停用应用验证。因此,您可以在这些流程中进行自动测试,并且实现起来更加容易。此外,借助这些 API,您还可以在 Android 上测试即时验证流程。

在网页上,请将 appVerificationDisabledForTesting 设置为 true(在呈现 firebase.auth.RecaptchaVerifier 之前设置)。这会自动解析 reCAPTCHA,因此您无需手动解析即可传递电话号码。请注意,即使已停用 reCAPTCHA,使用未列入白名单的电话号码仍无法完成登录。只有已列入白名单的电话号码才能与此 API 配合使用。

// Turn off phone auth app verification.
firebase.auth().settings.appVerificationDisabledForTesting = true;

var phoneNumber = "+16505554567";
var testVerificationCode = "123456";

// This will render a fake reCAPTCHA as appVerificationDisabledForTesting is true.
// This will resolve after rendering without app verification.
var appVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container');
// signInWithPhoneNumber will call appVerifier.verify() which will resolve with a fake
// reCAPTCHA response.
firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
    .then(function (confirmationResult) {
      // confirmationResult can resolve with the whitelisted testVerificationCode above.
      return confirmationResult.confirm(testVerificationCode).
    }).catch(function (error) {
      // Error; SMS not sent
      // ...
    });

停用应用验证后,可见和不可见的模拟 reCAPTCHA 应用验证程序的行为有所不同:

  • 可见的 reCAPTCHA:当可见的 reCAPTCHA 通过 appVerifier.render() 呈现时,它会在几分之一秒的延迟后自动解析。这相当于用户在呈现时立即点击 reCAPTCHA。reCAPTCHA 响应将在一段时间后过期,然后再次自动解析。
  • 不可见的 reCAPTCHA:不可见的 reCAPTCHA 不会在呈现时自动解析,而会在 appVerifier.verify() 被调用或 reCAPTCHA 的按钮锚被点击后延迟几分之一秒自动解析。同样,reCAPTCHA 响应也会在一段时间后过期,并且只会在 appVerifier.verify() 被调用或 reCAPTCHA 的按钮锚被再次点击后自动解析。

模拟 reCAPTCHA 每次得到解析时,系统都会按照预期触发相应的回调函数并给出虚假响应。如果还指定了 expiration 回调函数,则系统会在响应到期时触发此回调函数。

后续步骤

在用户首次登录后,系统会创建一个新的用户帐号,并将其与该用户登录时使用的凭据(即用户名、密码、电话号码或者身份验证提供方信息)相关联。此新帐号存储在您的 Firebase 项目中,无论用户采用何种方式登录,您项目中的每个应用都可以使用此帐号来识别用户。

  • 在您的应用中,建议采用在 Auth 对象上设置观察者的方式来了解用户的身份验证状态。您可以从 User 对象获取用户的个人资料基本信息。请参阅管理用户

  • 在您的 Firebase 实时数据库和 Cloud Storage 安全规则中,您可以从 auth 变量获取已登录用户的唯一用户 ID,然后用此 ID 来控制用户可以访问哪些数据。

您可以通过将身份验证提供方凭据关联至现有用户帐号,让用户可以使用多个身份验证提供方服务登录您的应用。

要让用户退出帐号,请调用 signOut

firebase.auth().signOut().then(function() {
  // Sign-out successful.
}).catch(function(error) {
  // An error happened.
});

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面