您可以使用 Firebase 身份验证通过向用户发送包含链接的电子邮件来登录用户,他们可以单击该链接进行登录。在此过程中,还会验证用户的电子邮件地址。
通过电子邮件登录有很多好处:
- 低摩擦注册和登录。
- 降低跨应用程序重复使用密码的风险,这可能会破坏即使是精心挑选的密码的安全性。
- 能够验证用户身份,同时验证用户是否是电子邮件地址的合法所有者。
- 用户只需要一个可访问的电子邮件帐户即可登录。不需要拥有电话号码或社交媒体帐户。
- 用户无需提供(或记住)密码即可安全登录,这在移动设备上可能很麻烦。
- 以前使用电子邮件标识符(密码或联合身份)登录的现有用户可以升级为仅使用电子邮件登录。例如,忘记密码的用户仍然可以登录而无需重置密码。
在你开始之前
如果您尚未将初始化代码段从Firebase 控制台复制到您的项目中,请按照将Firebase 添加到 JavaScript 项目中的说明进行操作。
为您的 Firebase 项目启用电子邮件链接登录
要通过电子邮件链接登录用户,您必须首先为您的 Firebase 项目启用电子邮件提供商和电子邮件链接登录方法:
- 在Firebase 控制台中,打开Auth部分。
- 在登录方法选项卡上,启用电子邮件/密码提供程序。请注意,必须启用电子邮件/密码登录才能使用电子邮件链接登录。
- 在同一部分中,启用电子邮件链接(无密码登录)登录方法。
- 单击保存。
向用户的电子邮件地址发送身份验证链接
要启动身份验证流程,请向用户显示一个界面,提示用户提供他们的电子邮件地址,然后调用sendSignInLinkToEmail
以请求 Firebase 将身份验证链接发送到用户的电子邮件。
构造
ActionCodeSettings
对象,该对象为 Firebase 提供有关如何构造电子邮件链接的说明。设置以下字段:-
url
:要嵌入的深层链接和要传递的任何其他状态。必须将链接的域添加到授权域的 Firebase 控制台列表中,可以通过转到登录方法选项卡(身份验证 -> 登录方法)找到该列表。 -
android
和ios
:在 Android 或 Apple 设备上打开登录链接时使用的应用程序。详细了解如何配置 Firebase 动态链接以通过移动应用打开电子邮件操作链接。 -
handleCodeInApp
:设置为真。与其他带外电子邮件操作(密码重置和电子邮件验证)不同,登录操作必须始终在应用程序中完成。这是因为,在流程结束时,预计用户会登录,并且他们的身份验证状态会保留在应用程序中。 dynamicLinkDomain
:当为一个项目定义了多个自定义动态链接域时,指定在通过指定的移动应用程序(例如example.page.link
)打开链接时使用哪一个。否则将自动选择第一个域。Web version 9
const actionCodeSettings = { // URL you want to redirect back to. The domain (www.example.com) for this // URL must be in the authorized domains list in the Firebase Console. url: 'https://www.example.com/finishSignUp?cartId=1234', // This must be true. handleCodeInApp: true, iOS: { bundleId: 'com.example.ios' }, android: { packageName: 'com.example.android', installApp: true, minimumVersion: '12' }, dynamicLinkDomain: 'example.page.link' };
Web version 8
var actionCodeSettings = { // URL you want to redirect back to. The domain (www.example.com) for this // URL must be in the authorized domains list in the Firebase Console. url: 'https://www.example.com/finishSignUp?cartId=1234', // This must be true. handleCodeInApp: true, iOS: { bundleId: 'com.example.ios' }, android: { packageName: 'com.example.android', installApp: true, minimumVersion: '12' }, dynamicLinkDomain: 'example.page.link' };
要了解有关 ActionCodeSettings 的更多信息,请参阅电子邮件操作中的传递状态部分。
-
向用户询问他们的电子邮件。
将认证链接发送到用户邮箱,并保存用户邮箱,以防用户在同一设备上完成邮箱登录。
Web version 9
import { getAuth, sendSignInLinkToEmail } from "firebase/auth"; const auth = getAuth(); sendSignInLinkToEmail(auth, email, actionCodeSettings) .then(() => { // The link was successfully sent. Inform the user. // Save the email locally so you don't need to ask the user for it again // if they open the link on the same device. window.localStorage.setItem('emailForSignIn', email); // ... }) .catch((error) => { const errorCode = error.code; const errorMessage = error.message; // ... });
Web version 8
firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings) .then(() => { // The link was successfully sent. Inform the user. // Save the email locally so you don't need to ask the user for it again // if they open the link on the same device. window.localStorage.setItem('emailForSignIn', email); // ... }) .catch((error) => { var errorCode = error.code; var errorMessage = error.message; // ... });
使用电子邮件链接完成登录
安全问题
为防止使用登录链接以非预期用户身份或在非预期设备上登录,Firebase 身份验证要求在完成登录流程时提供用户的电子邮件地址。要成功登录,此电子邮件地址必须与最初发送登录链接的地址相匹配。
当您发送登录电子邮件时,您可以通过在本地存储他们的电子邮件地址(例如使用 localStorage 或 cookie)来为在请求链接的同一设备上打开登录链接的用户简化此流程。然后,使用此地址完成流程。不要在重定向 URL 参数中传递用户的电子邮件并重新使用它,因为这可能会启用会话注入。
登录完成后,任何以前未经验证的登录机制都将从用户中删除,任何现有会话都将失效。例如,如果有人之前使用相同的电子邮件和密码创建了一个未经验证的帐户,则该用户的密码将被删除,以防止声称拥有所有权并创建该未经验证的帐户的冒充者使用未经验证的电子邮件和密码再次登录。
还要确保您在生产中使用 HTTPS URL,以避免您的链接可能被中间服务器拦截。
在网页中完成登录
电子邮件链接深层链接的格式与用于带外电子邮件操作(电子邮件验证、密码重置和电子邮件更改撤销)的格式相同。 Firebase Auth 通过提供isSignInWithEmailLink
API 检查链接是否为使用电子邮件登录链接来简化此检查。
要在登录页面上完成登录,请使用用户的电子邮件和包含一次性代码的实际电子邮件链接调用signInWithEmailLink
。
Web version 9
import { getAuth, isSignInWithEmailLink, signInWithEmailLink } from "firebase/auth"; // Confirm the link is a sign-in with email link. const auth = getAuth(); if (isSignInWithEmailLink(auth, window.location.href)) { // Additional state parameters can also be passed via URL. // This can be used to continue the user's intended action before triggering // the sign-in operation. // Get the email if available. This should be available if the user completes // the flow on the same device where they started it. let email = window.localStorage.getItem('emailForSignIn'); if (!email) { // User opened the link on a different device. To prevent session fixation // attacks, ask the user to provide the associated email again. For example: email = window.prompt('Please provide your email for confirmation'); } // The client SDK will parse the code from the link for you. signInWithEmailLink(auth, email, window.location.href) .then((result) => { // Clear email from storage. window.localStorage.removeItem('emailForSignIn'); // You can access the new user via result.user // Additional user info profile not available via: // result.additionalUserInfo.profile == null // You can check if the user is new or existing: // result.additionalUserInfo.isNewUser }) .catch((error) => { // Some error occurred, you can inspect the code: error.code // Common errors could be invalid email and invalid or expired OTPs. }); }
Web version 8
// Confirm the link is a sign-in with email link. if (firebase.auth().isSignInWithEmailLink(window.location.href)) { // Additional state parameters can also be passed via URL. // This can be used to continue the user's intended action before triggering // the sign-in operation. // Get the email if available. This should be available if the user completes // the flow on the same device where they started it. var email = window.localStorage.getItem('emailForSignIn'); if (!email) { // User opened the link on a different device. To prevent session fixation // attacks, ask the user to provide the associated email again. For example: email = window.prompt('Please provide your email for confirmation'); } // The client SDK will parse the code from the link for you. firebase.auth().signInWithEmailLink(email, window.location.href) .then((result) => { // Clear email from storage. window.localStorage.removeItem('emailForSignIn'); // You can access the new user via result.user // Additional user info profile not available via: // result.additionalUserInfo.profile == null // You can check if the user is new or existing: // result.additionalUserInfo.isNewUser }) .catch((error) => { // Some error occurred, you can inspect the code: error.code // Common errors could be invalid email and invalid or expired OTPs. }); }
在移动应用程序中完成登录
Firebase 身份验证使用 Firebase 动态链接将电子邮件链接发送到移动设备。对于通过移动应用程序完成登录,必须将应用程序配置为检测传入的应用程序链接,解析底层深层链接,然后像通过 Web 流完成登录一样完成登录。
要了解有关如何在 Android 应用程序中处理使用电子邮件链接登录的更多信息,请参阅Android 指南。
要了解更多关于如何在 Apple 应用程序中处理使用电子邮件链接登录的信息,请参阅Apple 平台指南。
使用电子邮件链接链接/重新验证
您还可以将此身份验证方法链接到现有用户。例如,以前通过另一个提供商(例如电话号码)进行身份验证的用户可以将此登录方法添加到他们现有的帐户中。
不同之处在于操作的后半部分:
Web version 9
import { getAuth, linkWithCredential, EmailAuthProvider } from "firebase/auth"; // Construct the email link credential from the current URL. const credential = EmailAuthProvider.credentialWithLink( email, window.location.href); // Link the credential to the current user. const auth = getAuth(); linkWithCredential(auth.currentUser, credential) .then((usercred) => { // The provider is now successfully linked. // The phone user can now sign in with their phone number or email. }) .catch((error) => { // Some error occurred. });
Web version 8
// Construct the email link credential from the current URL. var credential = firebase.auth.EmailAuthProvider.credentialWithLink( email, window.location.href); // Link the credential to the current user. firebase.auth().currentUser.linkWithCredential(credential) .then((usercred) => { // The provider is now successfully linked. // The phone user can now sign in with their phone number or email. }) .catch((error) => { // Some error occurred. });
这也可用于在运行敏感操作之前重新验证电子邮件链接用户。
Web version 9
import { getAuth, reauthenticateWithCredential, EmailAuthProvider } from "firebase/auth"; // Construct the email link credential from the current URL. const credential = EmailAuthProvider.credentialWithLink( email, window.location.href); // Re-authenticate the user with this credential. const auth = getAuth(); reauthenticateWithCredential(auth.currentUser, credential) .then((usercred) => { // The user is now successfully re-authenticated and can execute sensitive // operations. }) .catch((error) => { // Some error occurred. });
Web version 8
// Construct the email link credential from the current URL. var credential = firebase.auth.EmailAuthProvider.credentialWithLink( email, window.location.href); // Re-authenticate the user with this credential. firebase.auth().currentUser.reauthenticateWithCredential(credential) .then((usercred) => { // The user is now successfully re-authenticated and can execute sensitive // operations. }) .catch((error) => { // Some error occurred. });
但是,由于流程可能最终在原始用户未登录的不同设备上结束,因此该流程可能无法完成。在这种情况下,可以向用户显示错误以强制他们在同一设备上打开链接。可以在链接中传递一些状态以提供有关操作类型和用户 uid 的信息。
将电子邮件/密码与电子邮件链接区分开来
如果您同时支持密码登录和基于链接的电子邮件登录,为了区分密码/链接用户的登录方法,请使用fetchSignInMethodsForEmail
。这对于首先要求用户提供其电子邮件然后向其显示登录方法的标识符优先流程很有用:
Web version 9
import { getAuth, fetchSignInMethodsForEmail, EmailAuthProvider} from "firebase/auth"; // After asking the user for their email. const email = window.prompt('Please provide your email'); const auth = getAuth(); fetchSignInMethodsForEmail(auth, email) .then((signInMethods) => { // This returns the same array as fetchProvidersForEmail but for email // provider identified by 'password' string, signInMethods would contain 2 // different strings: // 'emailLink' if the user previously signed in with an email/link // 'password' if the user has a password. // A user could have both. if (signInMethods.indexOf(EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) { // User can sign in with email/password. } if (signInMethods.indexOf(EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) { // User can sign in with email/link. } }) .catch((error) => { // Some error occurred, you can inspect the code: error.code });
Web version 8
// After asking the user for their email. var email = window.prompt('Please provide your email'); firebase.auth().fetchSignInMethodsForEmail(email) .then((signInMethods) => { // This returns the same array as fetchProvidersForEmail but for email // provider identified by 'password' string, signInMethods would contain 2 // different strings: // 'emailLink' if the user previously signed in with an email/link // 'password' if the user has a password. // A user could have both. if (signInMethods.indexOf( firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) { // User can sign in with email/password. } if (signInMethods.indexOf( firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) { // User can sign in with email/link. } }) .catch((error) => { // Some error occurred, you can inspect the code: error.code });
如上所述,电子邮件/密码和电子邮件/链接被视为具有不同登录方法的相同firebase.auth.EmailAuthProvider
(相同PROVIDER_ID
)。
下一步
用户首次登录后,会创建一个新用户帐户并将其链接到凭据(即用户名和密码、电话号码或身份验证提供商信息),即用户登录时使用的凭据。这个新帐户作为 Firebase 项目的一部分存储,可用于在项目中的每个应用中识别用户,无论用户如何登录。
在您的应用程序中,了解用户身份验证状态的推荐方法是在
Auth
对象上设置观察者。然后,您可以从User
对象中获取用户的基本配置文件信息。请参阅管理用户。在您的 Firebase 实时数据库和云存储安全规则中,您可以从
auth
变量中获取登录用户的唯一用户 ID,并使用它来控制用户可以访问哪些数据。
您可以通过将身份验证提供程序凭据链接到现有用户帐户来允许用户使用多个身份验证提供程序登录您的应用程序。
要注销用户,请调用signOut
:
Web version 9
import { getAuth, signOut } from "firebase/auth"; const auth = getAuth(); signOut(auth).then(() => { // Sign-out successful. }).catch((error) => { // An error happened. });
Web version 8
firebase.auth().signOut().then(() => { // Sign-out successful. }).catch((error) => { // An error happened. });