如果您已升级到 Firebase Authentication with Identity Platform,则可以将 SMS 多重身份验证添加到您的 Web 应用程序。
多重身份验证可提高应用程序的安全性。虽然攻击者通常会泄露密码和社交帐户,但拦截短信却更加困难。
在你开始之前
启用至少一个支持多重身份验证的提供商。每个提供商都支持 MFA,但电话身份验证、匿名身份验证和 Apple Game Center 除外。
确保您的应用正在验证用户电子邮件。 MFA 需要电子邮件验证。这可以防止恶意行为者使用不属于他们的电子邮件注册服务,然后通过添加第二个因素来锁定真正的所有者。
使用多租户
如果要在多租户环境中启用多重身份验证,请确保完成以下步骤(除了本文档中的其余说明之外):
在 GCP Console 中,选择您要使用的租户。
在您的代码中,将
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';
启用多重身份验证
打开 Firebase 控制台的身份验证 > 登录方法页面。
在Advanced部分,启用SMS Multi-factor Authentication 。
您还应该输入您将用来测试您的应用程序的电话号码。虽然是可选的,但强烈建议注册测试电话号码以避免在开发过程中受到限制。
如果您尚未授权应用的域,请将其添加到 Firebase 控制台的“身份验证”>“设置”页面上的允许列表。
选择注册模式
您可以选择您的应用程序是否需要多重身份验证,以及如何以及何时注册您的用户。一些常见的模式包括:
注册用户的第二因素作为注册的一部分。如果您的应用程序需要对所有用户进行多重身份验证,请使用此方法。
提供可跳过的选项以在注册期间注册第二个因素。想要鼓励但不要求多重身份验证的应用程序可能更喜欢这种方法。
提供从用户帐户或个人资料管理页面而不是注册屏幕添加第二因素的能力。这最大限度地减少了注册过程中的摩擦,同时仍然为对安全敏感的用户提供多因素身份验证。
当用户想要访问具有更高安全要求的功能时,需要逐渐添加第二个因素。
设置 reCAPTCHA 验证程序
在发送 SMS 代码之前,您需要配置 reCAPTCHA 验证程序。 Firebase 使用 reCAPTCHA 来确保电话号码验证请求来自应用允许的域之一,从而防止滥用。
您无需手动设置 reCAPTCHA 客户端;客户端 SDK 的RecaptchaVerifier
对象自动创建和初始化任何必要的客户端密钥和机密。
使用不可见的 reCAPTCHA
RecaptchaVerifier
对象支持不可见的 reCAPTCHA ,它通常可以在不需要任何交互的情况下验证用户。要使用不可见的 reCAPTCHA,请创建一个size
参数设置为invisible
的RecaptchaVerifier
,并指定启动多因素注册的 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);
RecaptchaVerifier 使用verify方法抽象出这个逻辑,所以你不需要直接处理grecaptcha
变量。
注册第二个因素
要为用户注册新的次要因素:
重新验证用户。
要求用户输入他们的电话号码。
如上一节所述初始化 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');
为用户获取多因素会话:
Web version 9
import { multiFactor } from "firebase/auth"; multiFactor(user).getSession().then(function (multiFactorSession) { // ... });
Web version 8
user.multiFactor.getSession().then(function(multiFactorSession) { // ... })
使用用户的电话号码和多因素会话初始化
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 };
向用户手机发送验证信息:
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 消息,并且适用标准费率。
如果请求失败,请重置 reCAPTCHA,然后重复上一步以便用户重试。请注意,
verifyPhoneNumber()
会在抛出错误时自动重置 reCAPTCHA,因为 reCAPTCHA 令牌只能一次性使用。Web version 9
recaptchaVerifier.clear();
Web version 8
recaptchaVerifier.clear();
发送短信代码后,请用户验证代码:
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);
使用
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);
完成注册。或者,您可以为第二个因素指定显示名称。这对于具有多个第二因素的用户很有用,因为电话号码在身份验证流程中被屏蔽(例如,+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);
});
恭喜!您已成功为用户注册第二个身份验证因素。
使用第二因素登录用户
要使用双因素短信验证登录用户:
使用他们的第一个因素让用户登录,然后捕获
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()
后捕获错误。如果用户注册了多个次要因素,请询问他们使用哪一个:
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. }
如上一节所述初始化 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');
使用用户的电话号码和多因素会话初始化
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 };
向用户手机发送验证信息:
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. })
如果请求失败,请重置 reCAPTCHA,然后重复上一步以便用户重试:
Web version 9
recaptchaVerifier.clear();
Web version 8
recaptchaVerifier.clear();
发送短信代码后,请用户验证代码:
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);
使用
PhoneAuthCredential
初始化MultiFactorAssertion
对象:Web version 9
const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
Web version 8
var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
调用
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.
} ...
});
恭喜!您已使用多重身份验证成功登录用户。
下一步是什么
- 使用 Admin SDK 以编程方式管理多因素用户。