使用 JavaScript 中的電子郵件連結透過 Firebase 進行身份驗證

您可以使用 Firebase 驗證來登入用戶,方法是向用戶發送一封包含連結的電子郵件,用戶可以點擊該連結進行登入。在此過程中,也會驗證用戶的電子郵件地址。

透過電子郵件登入有很多好處:

  • 低摩擦註冊和登入。
  • 降低跨應用程式重複使用密碼的風險,即使精心選擇的密碼也可能會破壞其安全性。
  • 能夠對使用者進行身份驗證,同時驗證使用者是否為電子郵件地址的合法所有者。
  • 使用者只需要一個可存取的電子郵件帳戶即可登入。不需要電話號碼或社交媒體帳戶的所有權。
  • 用戶可以安全登錄,無需提供(或記住)密碼,這在行動裝置上可能很麻煩。
  • 先前使用電子郵件識別碼(密碼或聯合)登入的現有使用者可以升級為僅使用電子郵件登入。例如,忘記密碼的用戶仍然可以登錄,而無需重置密碼。

在你開始之前

如果您尚未將初始化程式碼片段從Firebase 控制台複製到您的項目,請依照將 Firebase 新增至您的 JavaScript 專案中所述。

若要透過電子郵件連結登入用戶,您必須先為您的 Firebase 專案啟用電子郵件提供者和電子郵件連結登入方法:

  1. Firebase 控制台中,開啟「驗證」部分。
  2. 登入方法標籤上,啟用電子郵件/密碼提供者。請注意,必須啟用電子郵件/密碼登入才能使用電子郵件連結登入。
  3. 在同一部分中,啟用電子郵件連結(無密碼登入)登入方法。
  4. 按一下「儲存」

若要啟動身分驗證流程,請向使用者顯示介面,提示使用者提供電子郵件地址,然後呼叫sendSignInLinkToEmail請求 Firebase 將驗證連結傳送到使用者的電子郵件。

  1. 建構ActionCodeSettings對象,該對象為 Firebase 提供有關如何建構電子郵件連結的說明。設定以下欄位:

    • url :要嵌入的深層連結以及要傳遞的任何其他狀態。該連結的網域必須新增到 Firebase 控制台的授權網域清單中,可以透過前往登入方法標籤(驗證 -> 設定)找到該清單。
    • androidios :在 Android 或 Apple 裝置上開啟登入連結時要使用的應用程式。詳細了解如何配置 Firebase 動態連結以透過行動應用程式開啟電子郵件操作連結。
    • handleCodeInApp :設定為 true。與其他帶外電子郵件操作(密碼重設和電子郵件驗證)不同,登入操作必須始終在應用程式中完成。這是因為,在流程結束時,使用者應該登入並且他們的身份驗證狀態會保留在應用程式中。
    • dynamicLinkDomain :當為專案定義多個自訂動態連結網域時,指定透過指定的行動應用程式開啟連結時要使用哪一個(例如example.page.link )。否則將自動選擇第一個網域。

      Web modular API

      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 namespaced API

      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 的更多信息,請參閱電子郵件操作中的傳遞狀態部分。

  2. 詢問使用者的電子郵件。

  3. 將身分驗證連結傳送到使用者的電子郵件,並儲存使用者的電子郵件,以防使用者在同一裝置上完成電子郵件登入。

    Web modular API

    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 namespaced API

    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),為在請求連結的相同裝置上開啟登入連結的使用者簡化此流程。然後,使用這個位址來完成流程。不要在重定向 URL 參數中傳遞使用者的電子郵件並重複使用它,因為這可能會啟用會話注入。

登入完成後,任何先前未經驗證的登入機制都將從使用者中刪除,並且任何現有會話都將失效。例如,如果某人之前使用相同的電子郵件和密碼建立了未經驗證的帳戶,則該使用者的密碼將被刪除,以防止聲稱擁有所有權並創建該未經驗證的帳戶的冒充者使用未經驗證的電子郵件和密碼再次登入。

另請確保在生產中使用 HTTPS URL,以避免您的連結可能被中間伺服器攔截。

完成網頁登入

電子郵件連結深層連結的格式與帶外電子郵件操作(電子郵件驗證、密碼重設和電子郵件變更撤銷)所使用的格式相同。 Firebase Auth 透過提供isSignInWithEmailLink API 來檢查連結是否是使用電子郵件連結登錄,從而簡化了此檢查。

要完成登錄頁面的登錄,請使用使用者的電子郵件地址和包含一次性程式碼的實際電子郵件連結來呼叫signInWithEmailLink

Web modular API

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 namespaced API

// 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 modular API

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 namespaced API

// 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 modular API

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 namespaced API

// 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()方法,我們先前建議使用該方法來實作識別碼優先的流程。

儘管您可以為項目停用電子郵件枚舉保護,但我們建議不要這樣做。

有關更多詳細信息,請參閱有關電子郵件枚舉保護的文件。

用於連結登入的預設電子郵件模板

預設電子郵件範本在主題和電子郵件正文中包含時間戳,以便後續電子郵件不會折疊成單一線程,並且連結會被隱藏

此範本適用於以下語言:

程式碼語言
阿爾阿拉伯
zh-CN簡體中文)
zh-TW中國傳統的)
NL荷蘭語
zh英語
en-GB英語(英國)
FR法語
德文
ID印尼
義大利語
日本人
韓國人
PL拋光
PT-BR葡萄牙語(巴西)
PT-PT葡萄牙語(葡萄牙)
俄文
英語西班牙語
es-419西班牙語(拉丁美洲)
th泰國

下一步

使用者首次登入後,系統會建立新的使用者帳戶,並將其連結到使用者登入時所使用的憑證(即使用者名稱和密碼、電話號碼或驗證提供者資訊)。此新帳戶將作為 Firebase 專案的一部分存儲,並且可用於識別專案中每個應用程式中的用戶,無論用戶如何登入。

  • 在您的應用程式中,了解使用者身份驗證狀態的建議方法是在Auth物件上設定觀察者。然後,您可以從User物件中取得使用者的基本個人資料資訊。請參閱管理用戶

  • 在 Firebase 即時資料庫和雲端儲存安全性規則中,您可以從auth變數取得登入使用者的唯一使用者 ID,並使用它來控制使用者可以存取哪些資料。

您可以透過將身分驗證提供者憑證連結到現有使用者帳戶,允許使用者使用多個驗證提供者登入您的應用程式。

若要登出用戶,請呼叫signOut

Web modular API

import { getAuth, signOut } from "firebase/auth";

const auth = getAuth();
signOut(auth).then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});

Web namespaced API

firebase.auth().signOut().then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});