使用 JavaScript 中的電子郵件鏈接通過 Firebase 進行身份驗證

您可以使用 Firebase 身份驗證通過向用戶發送一封包含鏈接的電子郵件來登錄用戶,他們可以單擊該鏈接進行登錄。在此過程中,用戶的電子郵件地址也會得到驗證。

通過電子郵件登錄有很多好處:

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

在你開始之前

如果您還沒有,請將初始化片段從Firebase 控制台複製到您的項目,如將 Firebase 添加到您的 JavaScript 項目中所述。

要通過電子郵件鏈接登錄用戶,您必須首先為您的 Firebase 項目啟用電子郵件提供商和電子郵件鏈接登錄方法:

  1. Firebase 控制台中,打開Auth部分。
  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 的信息。

如果您同時支持使用電子郵件登錄密碼和基於鏈接的登錄,要區分密碼/鏈接用戶的登錄方法,請使用fetchSignInMethodsForEmail 。這對於首先要求用戶提供他們的電子郵件然後提供登錄方法的標識符優先流程很有用:

Web modular API

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

// 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 Realtime Database 和 Cloud Storage Security Rules中,您可以從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.
});