您可以使用 Firebase 驗證功能,向使用者傳送含有連結的電子郵件,讓他們點選即可登入。在過程中,系統也會驗證使用者的電子郵件地址。
使用電子郵件登入有以下優點:
- 簡化註冊和登入程序。
- 降低應用程式之間重複使用密碼的風險,可能導致選取的密碼安全性不佳。
- 驗證使用者身分,同時驗證使用者是否為電子郵件地址的合法擁有者。
- 使用者只需具備可存取的電子郵件帳戶即可登入。不需要具備電話號碼或社群媒體帳戶的擁有權。
- 使用者不需提供 (或記住) 密碼,即可安全登入,這對行動裝置來說可能很麻煩。
- 先前以電子郵件 ID (密碼或聯合) 登入的現有使用者之升級,可以升級到僅使用電子郵件登入。舉例來說,如果使用者忘記密碼,仍然可以在不需要重設密碼的情況下登入。
事前準備
如果您尚未將初始化程式碼片段從 Firebase 主控台複製到專案,請按照「 將 Firebase 新增至 JavaScript 專案」一節中所述的方式操作。
為 Firebase 專案啟用電子郵件連結登入功能
如要透過電子郵件連結登入使用者,您必須先為 Firebase 專案啟用電子郵件服務供應商和電子郵件連結登入方式:
- 在 Firebase 控制台開啟「驗證」專區。
- 在「Sign in method」分頁中啟用「Email/Password」供應商。請注意,您必須啟用電子郵件/密碼登入功能,才能使用電子郵件連結登入。
- 在相同區段中,啟用「Email link (無密碼登入)」登入方式。
- 按一下「儲存」。
傳送驗證連結至使用者的電子郵件地址
如要啟動驗證流程,請向使用者顯示一個可提示使用者提供電子郵件地址的介面,然後呼叫 sendSignInLinkToEmail
以要求 Firebase 將驗證連結傳送至使用者的電子郵件。
建構
ActionCodeSettings
物件,向 Firebase 提供建構電子郵件連結的操作說明。設定下列欄位:url
:要嵌入的深層連結,以及要一併傳遞的任何其他狀態。您必須將連結的網域加入 Firebase 控制台的授權網域清單中,只要前往「登入方式」分頁 (依序點選「驗證」->「設定」),就能找到該網域。android
和ios
:在 Android 或 Apple 裝置上開啟登入連結時使用的應用程式。進一步瞭解如何設定 Firebase Dynamic Links,以透過行動應用程式開啟電子郵件動作連結。handleCodeInApp
:設為 true。登入作業必須一律在應用程式中完成,這一點與頻帶其他電子郵件動作不同 (密碼重設和電子郵件驗證)。這是因為在流程結束時,使用者應會登入,且驗證狀態也會保留在應用程式中。dynamicLinkDomain
:為專案定義多個自訂動態連結網域時,請指定當使用者透過指定的行動應用程式 (例如example.page.link
) 開啟連結時要使用的自訂動態連結網域,否則系統會自動選取第一個網域。Web
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
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
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
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),藉此簡化這項程序。接著,請使用這個地址完成流程。請勿在重新導向網址參數中傳遞使用者的電子郵件,然後重新使用,因為這可能會啟用工作階段插入功能。
登入完成後,所有先前未經驗證的登入機制都會從使用者中移除,且所有現有工作階段都將失效。例如,如果某人之前已使用相同電子郵件地址和密碼建立未經驗證的帳戶,系統會移除使用者的密碼,以防止冒用擁有權的冒用者在建立未經驗證的帳戶時,使用未經驗證的電子郵件和密碼重新登入。
此外,請務必在實際工作環境中使用 HTTPS 網址,避免中介伺服器攔截您的連結。
在網頁中完成登入程序
電子郵件連結深層連結的格式,與外流電子郵件操作所用的格式 (電子郵件驗證、密碼重設和電子郵件變更撤銷) 相同。Firebase 驗證可提供 isSignInWithEmailLink
API 來檢查連結是否為透過電子郵件連結登入,藉此簡化這項檢查。
如要完成到達網頁上的登入程序,請使用使用者的電子郵件地址和含有一次性代碼的實際電子郵件連結呼叫 signInWithEmailLink
。
Web
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 by importing getAdditionalUserInfo // and calling it with result: // getAdditionalUserInfo(result) // You can access the user's profile via: // getAdditionalUserInfo(result)?.profile // You can check if the user is new or existing: // getAdditionalUserInfo(result)?.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
// 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 動態連結將電子郵件連結傳送至行動裝置。如要透過行動應用程式完成登入,您必須將應用程式設為偵測收到的應用程式連結、剖析基礎深層連結,然後按照網頁流程完成登入程序。
如要進一步瞭解如何在 Android 應用程式中處理含有電子郵件連結的登入程序,請參閱 Android 指南。
如要進一步瞭解如何在 Apple 應用程式中處理電子郵件連結登入作業,請參閱 Apple 平台指南。
連結/重新驗證電子郵件連結
您也可以將這種驗證方法連結至現有的使用者。舉例來說,使用者先前已透過其他供應商 (例如電話號碼) 完成驗證,就能將這個登入方式新增至現有的帳戶。
差異如下:作業的後半部:
Web
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
// 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
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
// 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 的資訊。
已淘汰:區分電子郵件密碼與電子郵件連結
如果專案在 2023 年 9 月 15 日當天或之後建立,系統預設會啟用電子郵件列舉防護功能。這項功能可改善專案使用者帳戶的安全性,但會停用 fetchSignInMethodsForEmail()
方法 (先前建議用於實作 ID 優先流程)。
雖然您可以停用專案的電子郵件列舉防護功能,但建議您不要這麼做。
詳情請參閱電子郵件列舉防護功能說明文件。
連結登入的預設電子郵件範本
預設的電子郵件範本會在主旨和電子郵件內文中加入時間戳記,因此後續的電子郵件不會收合為單一執行緒,導致連結遭到隱藏。
這個範本適用於下列語言:
程式碼 | 語言 |
---|---|
ar | 阿拉伯文 |
簡體中文 | 中文 (簡體) |
zh-TW | 中文 (繁體) |
nl | 荷蘭文 |
en | 英文 |
en-GB | 英文 (英國) |
fr | 法文 |
de | 德文 |
ID | 印尼文 |
該資料來源 | 義大利文 |
ja | 日文 |
ko | 韓文 |
pl | 波蘭文 |
pt-BR | 葡萄牙文 (巴西) |
pt-PT | 葡萄牙文 (葡萄牙) |
ru | 俄語 |
es | 西班牙文 |
es-419 | 西班牙文 (拉丁美洲) |
th | 泰文 |
後續步驟
使用者首次登入時,系統會建立新的使用者帳戶,並連結至憑證 (即使用者名稱與密碼、電話號碼或驗證提供者資訊),也就是使用者登入時使用的憑證。這個新帳戶會儲存在您的 Firebase 專案中,可用來識別專案中各個應用程式的使用者 (無論使用者登入方式為何)。
-
如要在應用程式中瞭解使用者的驗證狀態,建議在
Auth
物件上設定觀察器。接著,您就能從User
物件取得使用者的基本個人資料資訊。請參閱管理使用者一文。 在 Firebase 即時資料庫和 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. });