您可以透過 Firebase 驗證功能,傳送內含連結的電子郵件給使用者,讓對方點選連結登入。這個程序也會驗證使用者的電子郵件地址。
使用電子郵件登入有許多好處:
- 輕鬆註冊及登入。
- 降低在應用程式中重複使用密碼的風險,即使密碼選得再好,重複使用仍可能導致安全性問題。
- 驗證使用者身分,同時確認使用者是電子郵件地址的合法擁有者。
- 使用者只要有可存取的電子郵件帳戶即可登入,不需擁有電話號碼或社群媒體帳戶。
- 使用者不必提供 (或記住) 密碼,即可安全登入,這在行動裝置上可能很麻煩。
- 如果現有使用者先前是透過電子郵件 ID (密碼或同盟) 登入,可以升級為僅使用電子郵件登入。舉例來說,使用者忘記密碼時,仍可登入,不必重設密碼。
事前準備
如果尚未複製,請從 Firebase 控制台將初始化程式碼片段複製到專案,如「將 Firebase 新增至 JavaScript 專案」一文所述。
為 Firebase 專案啟用電子郵件連結登入功能
如要透過電子郵件連結登入使用者,請先為 Firebase 專案啟用電子郵件供應商和電子郵件連結登入方法:
在「登入方式」分頁中,啟用「電子郵件地址/密碼」登入方式。 請注意,如要使用電子郵件連結登入,必須先啟用電子郵件地址/密碼登入功能。
在同一區段中,啟用「電子郵件連結 (不需要密碼即可登入)」登入供應商。
按一下 [儲存]。
將驗證連結傳送至使用者的電子郵件地址
如要啟動驗證流程,請向使用者顯示介面,提示使用者提供電子郵件地址,然後呼叫 sendSignInLinkToEmail,要求 Firebase 將驗證連結傳送至使用者的電子郵件地址。
建構
ActionCodeSettings物件,向 Firebase 提供電子郵件連結的建構方式。設定下列欄位:url:要嵌入的深層連結,以及要一併傳遞的任何其他狀態。如果尚未將網域加入授權網域清單,請按照下列步驟操作:在 Firebase 控制台中,依序前往「安全性」 >「驗證」 >「設定」分頁。
在「Authorized domains」(已授權網域) 區段,按一下「Add domain」(新增網域),並新增您的網域。
android和ios:協助 Firebase Authentication 判斷是否應建立僅限網頁或行動連結,並在 Android 或 Apple 裝置上開啟。handleCodeInApp:設為 true。與其他頻外電子郵件動作 (密碼重設和電子郵件驗證) 不同,登入作業一律必須在應用程式中完成。這是因為在流程結束時,使用者應已登入,且其驗證狀態會保留在應用程式中。linkDomain:為專案定義自訂 Hosting 連結網域時,請指定要使用哪個網域,以便透過特定行動應用程式開啟連結。否則系統會自動選取預設網域 (例如 )。PROJECT_ID.firebaseapp.comdynamicLinkDomain:已淘汰,請勿指定這個參數。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' }, // The domain must be configured in Firebase Hosting and owned by the project. linkDomain: 'custom-domain.com' };
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 Auth 會要求使用者在完成登入流程時提供電子郵件地址。如要順利登入,這個電子郵件地址必須與登入連結原始寄送的地址相符。
如果使用者在要求連結的同一部裝置上開啟登入連結,您可以在傳送登入電子郵件時,將他們的電子郵件地址儲存在本機 (例如使用 localStorage 或 Cookie),簡化這個流程。然後使用這個地址完成流程。請勿在重新導向網址參數中傳遞使用者的電子郵件地址,並重複使用該地址,因為這可能會導致工作階段注入。
登入完成後,系統會移除使用者先前未經驗證的登入機制,並使現有工作階段失效。舉例來說,如果有人先前使用相同電子郵件地址和密碼建立未經驗證的帳戶,系統會移除使用者的密碼,防止冒用者聲稱擁有該帳戶並建立未經驗證的帳戶,然後再次使用未經驗證的電子郵件地址和密碼登入。
此外,請務必在正式環境中使用 HTTPS 網址,以免連結遭到中繼伺服器攔截。
在網頁中完成登入
電子郵件連結深層連結的格式與用於頻外電子郵件動作的格式相同 (電子郵件驗證、重設密碼和電子郵件變更撤銷)。Firebase Auth 提供 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 Authentication 會使用 Firebase Hosting 將電子郵件連結傳送至行動裝置。如要透過行動應用程式完成登入,應用程式必須設定為偵測傳入的應用程式連結、剖析基礎深層連結,然後完成登入,就像透過網頁流程完成登入一樣。
如要進一步瞭解如何在 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 為優先的流程時使用這個方法。
雖然您可以為專案停用電子郵件列舉防護,但我們不建議這麼做。
詳情請參閱電子郵件列舉防護功能的說明文件。
內含登入連結的預設電子郵件範本
預設電子郵件範本會在主旨和電子郵件內文中加入時間戳記,這樣後續電子郵件就不會摺疊成單一會話串,連結也不會隱藏。
這個範本適用於下列語言:
| 程式碼 | 語言 |
|---|---|
| 阿拉伯文 | 阿拉伯文 |
| zh-CN | 簡體中文 |
| 中文 (繁體) | 繁體中文 (台灣) |
| 荷蘭文 | 荷蘭文 |
| en | 英文 |
| en-GB | 英文 (英國) |
| 法文 | 法文 |
| 德文 | 德文 |
| id | 印尼文 |
| 義大利文 | 義大利文 |
| 日文 | 日文 |
| ko | 韓文 |
| 波蘭文 | 波蘭文 |
| pt-BR | 葡萄牙文 (巴西) |
| 葡萄牙文 (葡萄牙) | 葡萄牙文 (葡萄牙) |
| 俄文 | 俄文 |
| 西班牙文 | 西班牙文 |
| es-419 | 西班牙文 (拉丁美洲) |
| 泰文 | 泰文 |
後續步驟
使用者首次登入後,系統會建立新的使用者帳戶,並連結至使用者登入時使用的憑證 (即使用者名稱和密碼、電話號碼或驗證供應商資訊)。這個新帳戶會儲存在 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. });