Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

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

您可以使用 Firebase 身份驗證通過向用戶發送包含鏈接的電子郵件來登錄用戶,用戶可以點擊該鏈接登錄。在此過程中,還會驗證用戶的電子郵件地址。

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

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

在你開始之前

如果你還沒有準備好,從初始化片段複製火力地堡控制台到您的項目中所述添加火力地堡到你的JavaScript項目

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

  1. 火力地堡控制台,打開驗證部分。
  2. 登錄方法選項卡,選中電子郵件/密碼提供商。請注意,必須啟用電子郵件/密碼登錄才能使用電子郵件鏈接登錄。
  3. 在相同的部分,使電子郵件的鏈接(無密碼登錄)登錄方法。
  4. 單擊保存

啟動認證流程,向用戶呈現一個界面,提示用戶提供他們的電子郵件地址,然後調用sendSignInLinkToEmail到請求火力地堡發送的驗證鏈接到用戶的電子郵件。

  1. 構建ActionCodeSettings對象,它提供火力地堡就如何構建電子郵件鏈接指令。設置以下字段:

    • url :深層鏈接嵌入和任何其他國家要傳承下去。鏈接的域必須添加到 Firebase 控制台的授權域列表中,可以通過轉到登錄方法選項卡(身份驗證 -> 登錄方法)找到該列表。
    • androidios :當登錄鏈接被打開Android或iOS設備上的應用使用。了解更多關於如何配置火力地堡動態鏈接通過移動應用程序打開電子郵件操作鏈接。
    • handleCodeInApp :設置為true。與其他帶外電子郵件操作(密碼重置和電子郵件驗證)不同,登錄操作必須始終在應用程序中完成。這是因為,在流程結束時,用戶需要登錄並且他們的 Auth 狀態會保留在應用程序中。
    • dynamicLinkDomain :當多個自定義動態鏈接結構域的一個項目中,指定當鏈路是通過指定的移動應用被打開使用哪一個(例如,定義example.page.link )。否則將自動選擇第一個域。

      網頁版 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'
      };

      網頁版 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,指的是穿過狀態的電子郵件操作部分。

  2. 向用戶詢問他們的電子郵件。

  3. 將身份驗證鏈接發送到用戶的電子郵件,並保存用戶的電子郵件,以防用戶在同一設備上完成電子郵件登錄。

    網頁版 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;
        // ...
      });

    網頁版 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,以避免您的鏈接可能被中間服務器攔截。

在網頁中完成登錄

電子郵件中的鏈接深層鏈接的格式是一樣的用於帶外電子郵件行動格式(電子郵件驗證,密碼重置和電子郵件改變或撤銷)。火力地堡驗證通過提供簡化了這一檢查isSignInWithEmailLink API來檢查鏈接是否是一個標誌,與電子郵件鏈接。

要完成登錄著陸頁上,調用signInWithEmailLink與用戶的電子郵件,並包含一次性代碼的實際電子郵件鏈接。

網頁版 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.
    });
}

網頁版 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的指南

要了解如何更多地集中在如何處理登錄,在iOS應用程序電子郵件中的鏈接,請參閱iOS指南

您還可以將此身份驗證方法鏈接到現有用戶。例如,之前通過其他提供商(例如電話號碼)進行身份驗證的用戶可以將此登錄方法添加到他們現有的帳戶中。

不同之處在於操作的後半部分:

網頁版 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.
  });

網頁版 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.
  });

這也可用於在運行敏感操作之前重新驗證電子郵件鏈接用戶。

網頁版 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.
  });

網頁版 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 。這對於標識符優先的流程很有用,在這種流程中,首先要求用戶提供他們的電子郵件,然後提供登錄方法:

網頁版 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
  });

網頁版 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對象。請參閱管理用戶

  • 在你的火力地堡實時數據庫和雲存儲安全規則,你可以得到簽署的,從用戶的唯一的用戶ID auth的變量,並用它來控制哪些數據的用戶可以訪問。

您可以允許用戶通過使用多個身份驗證提供登錄到您的應用程序連接身份驗證提供憑據到現有的用戶帳戶。

要註銷用戶,請撥打signOut

網頁版 9

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

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

網頁版 8

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