使用 Firebase Authentication,您可以向用户的手机发送短信以协助其登录,用户使用短信中包含的一次性验证码即可登录。
如需向应用中添加电话号码登录方式,最简单的方法就是使用 FirebaseUI。该库中包含一个普适性登录 widget,可为电话号码登录、基于密码登录和联合登录实现登录流程。本文档介绍了如何使用 Firebase SDK 实现电话号码登录流程。
准备工作
按照将 Firebase 添加到您的 JavaScript 项目中所述,从 Firebase 控制台将初始化代码段复制到您的项目(如果您尚未执行此操作)。安全考量
与其他可用的方法相比,仅使用电话号码进行身份验证的方法虽然便捷,但安全性较低,因为电话号码的所有权可以很容易地在用户之间转移。此外,在具有多份用户个人资料的设备上,任何一位可以接收短信的用户都能使用该设备的电话号码登录账号。
如果您选择在应用中使用电话号码登录方法,应同时提供更安全的登录方法,并将使用电话号码登录的安全隐患告知用户。
为 Firebase 项目启用电话号码登录方法
如需让用户能够通过短信登录,您必须先为 Firebase 项目启用电话号码登录方法,步骤如下:
- 在 Firebase 控制台中,打开 Authentication 部分。
- 在登录方法页面上,启用电话号码登录方法。
- 在同一页面上,如果 OAuth 重定向网域部分未列出将托管您应用的网域,请添加您的网域。 请注意,不允许将 localhost 用作电话身份验证的托管域名。
设置 reCAPTCHA 验证程序
在让用户使用其电话号码登录之前,您必须设置 Firebase 的 reCAPTCHA 验证程序。Firebase 使用 reCAPTCHA 来防止滥用行为,具体方法是确保电话号码验证请求来自应用允许的某个网域。
您无需手动设置 reCAPTCHA 客户端;在使用 Firebase SDK 的 RecaptchaVerifier
对象时,Firebase 会自动创建和处理任何必要的客户端密钥和密文。
RecaptchaVerifier
对象支持隐形 reCAPTCHA(通常用户无需进行任何操作便能验证身份)以及 reCAPTCHA widget(始终需要用户互动才能完成验证)。
在底层呈现的 reCAPTCHA 可以根据用户的偏好设置进行本地化,方法是在呈现 reCAPTCHA 之前更新身份验证实例中的语言代码。发送给用户的短信(其中包含验证码)也会相应地进行本地化。
Web
import { getAuth } from "firebase/auth"; const auth = getAuth(); auth.languageCode = 'it'; // To apply the default browser preference instead of explicitly setting it. // auth.useDeviceLanguage();
Web
firebase.auth().languageCode = 'it'; // To apply the default browser preference instead of explicitly setting it. // firebase.auth().useDeviceLanguage();
使用隐形 reCAPTCHA
如需使用隐形 reCAPTCHA,请创建一个 RecaptchaVerifier
对象,将 size
参数设置为 invisible
,并指定用于提交登录表单的按钮的 ID。例如:
Web
import { getAuth, RecaptchaVerifier } from "firebase/auth"; const auth = getAuth(); window.recaptchaVerifier = new RecaptchaVerifier(auth, 'sign-in-button', { 'size': 'invisible', 'callback': (response) => { // reCAPTCHA solved, allow signInWithPhoneNumber. onSignInSubmit(); } });
Web
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', { 'size': 'invisible', 'callback': (response) => { // reCAPTCHA solved, allow signInWithPhoneNumber. onSignInSubmit(); } });
使用 reCAPTCHA widget
如需使用可见的 reCAPTCHA widget,请在页面上创建一个包含此 widget 的元素,然后创建一个 RecaptchaVerifier
对象,并指定该容器的 ID。例如:
Web
import { getAuth, RecaptchaVerifier } from "firebase/auth"; const auth = getAuth(); window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {});
Web
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container');
可选:指定 reCAPTCHA 参数
您可以选择为 RecaptchaVerifier
对象设置回调函数,以便在用户解答 reCAPTCHA 时或 reCAPTCHA 在用户提交表单前到期的情况下调用:
Web
import { getAuth, RecaptchaVerifier } from "firebase/auth"; const auth = getAuth(); window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', { 'size': 'normal', 'callback': (response) => { // reCAPTCHA solved, allow signInWithPhoneNumber. // ... }, 'expired-callback': () => { // Response expired. Ask user to solve reCAPTCHA again. // ... } });
Web
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', { 'size': 'normal', 'callback': (response) => { // reCAPTCHA solved, allow signInWithPhoneNumber. // ... }, 'expired-callback': () => { // Response expired. Ask user to solve reCAPTCHA again. // ... } });
可选:预呈现 reCAPTCHA
如果您要在提交登录请求之前预呈现 reCAPTCHA,请调用 render
:
Web
recaptchaVerifier.render().then((widgetId) => { window.recaptchaWidgetId = widgetId; });
Web
recaptchaVerifier.render().then((widgetId) => { window.recaptchaWidgetId = widgetId; });
render
解析完成后,您会获得 reCAPTCHA 的微件 ID,此 ID 可用于调用 reCAPTCHA API:
Web
const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId);
Web
const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId);
向用户的电话发送验证码
如需启动电话号码登录,请向用户显示一个提示其输入电话号码的界面,然后调用 signInWithPhoneNumber
以请求 Firebase 通过短信向用户电话发送身份验证码:
-
获取用户的电话号码。
虽然相关的法律要求可能不尽相同,但为了避免用户不满,最佳做法是告知用户,如果他们选择使用电话号码登录方式,则可能会收到一条验证短信,并需按标准费率支付短信费用。
- 调用
signInWithPhoneNumber
,向其传递用户的电话号码和您之前创建的RecaptchaVerifier
。Web
import { getAuth, signInWithPhoneNumber } from "firebase/auth"; const phoneNumber = getPhoneNumberFromUserInput(); const appVerifier = window.recaptchaVerifier; const auth = getAuth(); signInWithPhoneNumber(auth, phoneNumber, appVerifier) .then((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((error) => { // Error; SMS not sent // ... });
Web
const phoneNumber = getPhoneNumberFromUserInput(); const appVerifier = window.recaptchaVerifier; firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier) .then((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((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 Authentication 向用户电话发送一条含验证码的短信。
使用验证码让用户登录
成功调用 signInWithPhoneNumber
后,提示用户输入自己通过短信收到的验证码。然后,将此验证码传递给 ConfirmationResult
对象的 confirm
方法(该对象已被传递给 signInWithPhoneNumber
的履行处理程序,即它的 then
块),使用户登录到账号。例如:
Web
const code = getCodeFromUserInput(); confirmationResult.confirm(code).then((result) => { // User signed in successfully. const user = result.user; // ... }).catch((error) => { // User couldn't sign in (bad verification code?) // ... });
Web
const code = getCodeFromUserInput(); confirmationResult.confirm(code).then((result) => { // User signed in successfully. const user = result.user; // ... }).catch((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().signInWithCredential(credential);
使用虚构的手机号码进行测试
您可以通过 Firebase 控制台设置虚构电话号码以用于开发。使用虚构的电话号码进行测试具有以下优势:
- 测试电话号码身份验证时不会占用使用量配额。
- 测试电话号码身份验证时无需发送实际的短信。
- 可使用同一电话号码运行连续测试,而不会受到短信发送数量的限制。如果审核人员恰好使用相同的电话号码进行测试,这项优势会使在 App Store 审核过程中遭拒的风险降至最低。
- 无需其他操作即可在开发环境中轻松进行测试,例如可以在 iOS 模拟器或没有 Google Play 服务的 Android 模拟器中进行开发。
- 可编写集成测试,而不会被通常在生产环境中应用于真实电话号码的安全检查所屏蔽。
虚构的电话号码必须满足以下要求:
- 确保使用确属虚构且不存在的电话号码。Firebase Authentication 不允许您将真实用户使用的现有电话号码设为测试号码。您可以使用以 555 为前缀的数字作为美国测试电话号码,例如 +1 650-555-3434
- 电话号码必须采用正确的格式,以符合长度要求和其他限制。这些电话号码仍将与真实用户的电话号码一样经过相同的验证。
- 您最多可以添加 10 个电话号码用于开发。
- 使用难以猜到的测试电话号码/验证码,并经常更换。
创建虚构的电话号码和验证码
- 在 Firebase 控制台中,打开 Authentication 部分。
- 在 Sign in method(登录方法)标签页中,启用电话号码提供方(如果您尚未启用)。
- 打开用于测试的电话号码折叠菜单。
- 提供您想要用于测试的电话号码,例如 +1 650-555-3434。
- 为该特定号码提供 6 位验证码,例如 654321。
- 添加该号码。如有需要,您可以删除电话号码及其验证码,只需将鼠标悬停在相应的行上并点击垃圾桶图标即可。
手动测试
您可以直接在应用中开始使用虚构的电话号码。这样一来,您就可以在开发阶段执行手动测试,而不会遇到配额问题或受到限制。您也可以直接通过 iOS 模拟器或未安装 Google Play 服务的 Android 模拟器进行测试。
当您提供虚构电话号码并发送验证码时,系统实际上不会发送短信。作为替代,您需要提供事先配置的验证码来完成登录。
完成登录后,系统会使用该电话号码创建一位 Firebase 用户。该用户的行为和属性与真实电话号码用户相同,并且可通过同样的方式使用 Realtime Database/Cloud Firestore 和其他服务。在此过程中生成的 ID 令牌与真实电话号码用户的令牌具有相同的签名。
您还可以利用自定义声明为此类用户设置测试角色,以将其作为虚构用户区分开来(如果您想进一步限制其访问权限)。
集成测试
除了手动测试外,Firebase Authentication 还提供了 API,帮助您编写用于进行电话号码身份验证测试的集成测试。这些 API 通过停用 reCAPTCHA 要求(在 Web 应用中)和静默推送通知(在 iOS 应用中)来停用应用验证。因此,您可以在这些流程中进行自动测试,并且实现起来更加容易。此外,借助这些 API,您还可以在 Android 上测试即时验证流程。
在 Web 上,请将 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 fictional 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 Realtime Database 和 Cloud Storage 安全规则中,您可以从
auth
变量获取已登录用户的唯一用户 ID,然后利用此 ID 来控制用户可以访问哪些数据。
您可以通过将身份验证提供方凭据关联至现有用户账号,让用户可以使用多个身份验证提供方登录您的应用。
如需将用户退出登录,请调用 signOut
:
Web
import { getAuth, signOut } from "firebase/auth"; const auth = getAuth(); signOut(auth).then(() => { // Sign-out successful. }).catch((error) => { // An error happened. });
Web
firebase.auth().signOut().then(() => { // Sign-out successful. }).catch((error) => { // An error happened. });