Join us in person and online for Firebase Summit on October 18, 2022. Learn how Firebase can help you accelerate app development, release your app with confidence, and scale with ease. Register now

使用 JavaScript 通過帶有電話號碼的 Firebase 進行身份驗證

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

您可以使用 Firebase 身份驗證通過向用戶手機發送 SMS 消息來登錄用戶。用戶使用 SMS 消息中包含的一次性代碼登錄。

將電話號碼登錄添加到您的應用程序的最簡單方法是使用FirebaseUI ,它包括一個插入式登錄小部件,用於實現電話號碼登錄以及基於密碼和聯合登錄的登錄流程-在。本文檔介紹如何使用 Firebase SDK 實現電話號碼登錄流程。

在你開始之前

如果您尚未將初始化代碼段從Firebase 控制台複製到您的項目中,請按照將Firebase 添加到 JavaScript 項目中的說明進行操作。

安全問題

僅使用電話號碼的身份驗證雖然方便,但不如其他可用方法安全,因為電話號碼的擁有可以在用戶之間輕鬆轉移。此外,在具有多個用戶配置文件的設備上,任何可以接收 SMS 消息的用戶都可以使用設備的電話號碼登錄帳戶。

如果您在應用程序中使用基於電話號碼的登錄,則應將其與更安全的登錄方法一起提供,並告知用戶使用電話號碼登錄的安全權衡。

為您的 Firebase 項目啟用電話號碼登錄

要通過短信登錄用戶,您必須首先為您的 Firebase 項目啟用電話號碼登錄方法:

  1. Firebase 控制台中,打開身份驗證部分。
  2. 登錄方式頁面,啟用電話號碼登錄方式。
  3. 在同一頁面上,如果將託管您的應用程序的域未列在OAuth 重定向域部分中,請添加您的域。

Firebase 的電話號碼登錄請求配額足夠高,大多數應用不會受到影響。但是,如果您需要使用電話身份驗證登錄大量用戶,則可能需要升級您的定價計劃。請參閱定價頁面。

設置 reCAPTCHA 驗證器

您必須先設置 Firebase 的 reCAPTCHA 驗證程序,然後才能使用他們的電話號碼登錄用戶。 Firebase 使用 reCAPTCHA 來防止濫用,例如確保電話號碼驗證請求來自您的應用允許的域之一。

您無需手動設置 reCAPTCHA 客戶端;當您使用 Firebase SDK 的RecaptchaVerifier對象時,Firebase 會自動創建和處理任何必要的客戶端密鑰和機密。

RecaptchaVerifier對象支持不可見的 reCAPTCHA ,它通常可以在不需要任何用戶操作的情況下驗證用戶,以及 reCAPTCHA 小部件,它總是需要用戶交互才能成功完成。

通過在呈現 reCAPTCHA 之前更新 Auth 實例上的語言代碼,可以將底層呈現的 reCAPTCHA 本地化為用戶的偏好。上述本地化也適用於發送給用戶的包含驗證碼的 SMS 消息。

Web version 9

import { getAuth } from "firebase/auth";

const auth = getAuth();
auth.languageCode = 'it';
// To apply the default browser preference instead of explicitly setting it.
// firebase.auth().useDeviceLanguage();

Web version 8

firebase.auth().languageCode = 'it';
// To apply the default browser preference instead of explicitly setting it.
// firebase.auth().useDeviceLanguage();

使用不可見的 reCAPTCHA

要使用不可見的 reCAPTCHA,請創建一個RecaptchaVerifier對象,並將size參數設置為invisible ,並指定提交登錄表單的按鈕的 ID。例如:

Web version 9

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

const auth = getAuth();
window.recaptchaVerifier = new RecaptchaVerifier('sign-in-button', {
  'size': 'invisible',
  'callback': (response) => {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    onSignInSubmit();
  }
}, auth);

Web version 8

window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
  'size': 'invisible',
  'callback': (response) => {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    onSignInSubmit();
  }
});

使用 reCAPTCHA 小部件

要使用可見的 reCAPTCHA 小部件,請在您的頁面上創建一個元素以包含該小部件,然後創建一個RecaptchaVerifier對象,並在您這樣做時指定容器的 ID。例如:

Web version 9

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

const auth = getAuth();
window.recaptchaVerifier = new RecaptchaVerifier('recaptcha-container', {}, auth);

Web version 8

window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container');

可選:指定 reCAPTCHA 參數

您可以選擇在RecaptchaVerifier對像上設置回調函數,當用戶解決 reCAPTCHA 或 reCAPTCHA 在用戶提交表單之前過期時調用:

Web version 9

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

const auth = getAuth();
window.recaptchaVerifier = new RecaptchaVerifier('recaptcha-container', {
  'size': 'normal',
  'callback': (response) => {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    // ...
  },
  'expired-callback': () => {
    // Response expired. Ask user to solve reCAPTCHA again.
    // ...
  }
}, auth);

Web version 8

window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', {
  'size': 'normal',
  'callback': (response) => {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    // ...
  },
  'expired-callback': () => {
    // Response expired. Ask user to solve reCAPTCHA again.
    // ...
  }
});

可選:預渲染 reCAPTCHA

如果您想在提交登錄請求之前預渲染 reCAPTCHA,請調用render

Web version 9

recaptchaVerifier.render().then((widgetId) => {
  window.recaptchaWidgetId = widgetId;
});

Web version 8

recaptchaVerifier.render().then((widgetId) => {
  window.recaptchaWidgetId = widgetId;
});

render解析後,您將獲得 reCAPTCHA 的小部件 ID,您可以使用它來調用reCAPTCHA API:

Web version 9

const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId);

Web version 8

const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId);

向用戶手機發送驗證碼

要啟動電話號碼登錄,請向用戶顯示一個界​​面,提示他們提供電話號碼,然後調用signInWithPhoneNumber請求 Firebase 通過 SMS 向用戶的手機發送驗證碼:

  1. 獲取用戶的電話號碼。

    法律要求各不相同,但作為最佳實踐並為您的用戶設定期望,您應該告知他們,如果他們使用電話登錄,他們可能會收到一條短信進行驗證,並適用標準費率。

  2. 調用signInWithPhoneNumber ,將用戶的電話號碼和您之前創建的RecaptchaVerifier傳遞給它。

    Web version 9

    import { getAuth, signInWithPhoneNumber } from "firebase/auth";
    
    const phoneNumber = getPhoneNumberFromUserInput();
    const appVerifier = window.recaptchaVerifier;
    
    const auth = getAuth();
    signInWithPhoneNumber(auth, phoneNumber, appVerifier)
        .then((confirmationResult) => {
          // SMS sent. Prompt user to type the code from the message, then sign the
          // user in with confirmationResult.confirm(code).
          window.confirmationResult = confirmationResult;
          // ...
        }).catch((error) => {
          // Error; SMS not sent
          // ...
        });

    Web version 8

    const phoneNumber = getPhoneNumberFromUserInput();
    const appVerifier = window.recaptchaVerifier;
    firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
        .then((confirmationResult) => {
          // SMS sent. Prompt user to type the code from the message, then sign the
          // user in with confirmationResult.confirm(code).
          window.confirmationResult = confirmationResult;
          // ...
        }).catch((error) => {
          // Error; SMS not sent
          // ...
        });
    如果signInWithPhoneNumber導致錯誤,請重置 reCAPTCHA,以便用戶可以重試:
    grecaptcha.reset(window.recaptchaWidgetId);
    
    // Or, if you haven't stored the widget ID:
    window.recaptchaVerifier.render().then(function(widgetId) {
      grecaptcha.reset(widgetId);
    });
    

signInWithPhoneNumber方法向用戶發出 reCAPTCHA 質詢,如果用戶通過質詢,則請求 Firebase 身份驗證向用戶的手機發送包含驗證碼的 SMS 消息。

使用驗證碼登錄用戶

調用signInWithPhoneNumber成功後,提示用戶輸入短信收到的驗證碼。然後,通過將代碼傳遞給ConfirmationResult對象的confirm方法來登錄用戶,該方法已傳遞給signInWithPhoneNumber的履行處理程序(即其then塊)。例如:

Web version 9

const code = getCodeFromUserInput();
confirmationResult.confirm(code).then((result) => {
  // User signed in successfully.
  const user = result.user;
  // ...
}).catch((error) => {
  // User couldn't sign in (bad verification code?)
  // ...
});

Web version 8

const code = getCodeFromUserInput();
confirmationResult.confirm(code).then((result) => {
  // User signed in successfully.
  const user = result.user;
  // ...
}).catch((error) => {
  // User couldn't sign in (bad verification code?)
  // ...
});

如果confirm調用成功,則用戶成功登錄。

獲取中間 AuthCredential 對象

如果您需要獲取用戶帳戶的AuthCredential對象,請將確認結果中的驗證碼和驗證碼傳遞給PhoneAuthProvider.credential ,而不是調用confirm :

var credential = firebase.auth.PhoneAuthProvider.credential(confirmationResult.verificationId, code);

然後,您可以使用憑據登錄用戶:

firebase.auth().signInWithCredential(credential);

使用虛構的電話號碼進行測試

您可以通過 Firebase 控制台設置用於開發的虛構電話號碼。使用虛構的電話號碼進行測試可帶來以下好處:

  • 在不消耗您的使用配額的情況下測試電話號碼身份驗證。
  • 在不發送實際 SMS 消息的情況下測試電話號碼身份驗證。
  • 使用相同的電話號碼運行連續測試而不會受到限制。如果審核者碰巧使用相同的電話號碼進行測試,這可以最大限度地降低 App Store 審核過程中被拒絕的風險。
  • 無需任何額外工作即可在開發環境中輕鬆進行測試,例如無需 Google Play 服務即可在 iOS 模擬器或 Android 模擬器中進行開發。
  • 編寫集成測試,而不會被通常應用於生產環境中真實電話號碼的安全檢查所阻止。

虛構電話號碼必須滿足以下要求:

  1. 確保您使用的電話號碼確實是虛構的,並且不存在。 Firebase 身份驗證不允許您將真實用戶使用的現有電話號碼設置為測試號碼。一種選擇是使用 555 前綴號碼作為美國測試電話號碼,例如: +1 650-555-3434
  2. 必須針對長度和其他限制正確格式化電話號碼。他們仍將通過與真實用戶電話號碼相同的驗證。
  3. 您最多可以添加 10 個電話號碼進行開發。
  4. 使用難以猜測的測試電話號碼/代碼並經常更改。

創建虛構的電話號碼和驗證碼

  1. Firebase 控制台中,打開身份驗證部分。
  2. 登錄方法選項卡中,啟用電話提供商(如果您尚未啟用)。
  3. 打開用於測試手風琴的電話號碼菜單。
  4. 提供您要測試的電話號碼,例如: +1 650-555-3434
  5. 提供該特定號碼的 6 位驗證碼,例如: 654321
  6. 添加號碼。如果需要,您可以通過將鼠標懸停在相應行上並單擊垃圾桶圖標來刪除電話號碼及其代碼。

手動測試

您可以直接在應用程序中開始使用虛構的電話號碼。這允許您在開發階段執行手動測試,而不會遇到配額問題或限制。您也可以直接從沒有安裝 Google Play 服務的 iOS 模擬器或 Android 模擬器進行測試。

當您提供虛構的電話號碼並發送驗證碼時,不會發送實際的短信。相反,您需要提供之前配置的驗證碼才能完成登錄。

登錄完成後,會使用該電話號碼創建一個 Firebase 用戶。用戶具有與真實電話號碼用戶相同的行為和屬性,並且可以以相同的方式訪問實時數據庫/Cloud Firestore 和其他服務。在此過程中鑄造的 ID 令牌與真實電話號碼用戶具有相同的簽名。

如果您想進一步限制訪問,另一種選擇是通過對這些用戶的自定義聲明設置測試角色,以將他們區分為假用戶。

集成測試

除了手動測試之外,Firebase 身份驗證還提供 API 來幫助編寫電話身份驗證測試的集成測試。這些 API 通過禁用 Web 中的 reCAPTCHA 要求和 iOS 中的靜默推送通知來禁用應用程序驗證。這使得這些流程中的自動化測試成為可能,並且更易於實施。此外,它們還有助於提供在 Android 上測試即時驗證流程的能力。

在 Web 上,在呈現firebase.auth.RecaptchaVerifier之前將appVerificationDisabledForTesting設置為true 。這會自動解析 reCAPTCHA,讓您無需手動解析即可傳遞電話號碼。請注意,即使 reCAPTCHA 已禁用,使用非虛構電話號碼仍將無法完成登錄。此 API 只能使用虛構電話號碼。

// Turn off phone auth app verification.
firebase.auth().settings.appVerificationDisabledForTesting = true;

var phoneNumber = "+16505554567";
var testVerificationCode = "123456";

// This will render a fake reCAPTCHA as appVerificationDisabledForTesting is true.
// This will resolve after rendering without app verification.
var appVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container');
// signInWithPhoneNumber will call appVerifier.verify() which will resolve with a fake
// reCAPTCHA response.
firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
    .then(function (confirmationResult) {
      // confirmationResult can resolve with the fictional testVerificationCode above.
      return confirmationResult.confirm(testVerificationCode)
    }).catch(function (error) {
      // Error; SMS not sent
      // ...
    });

禁用應用驗證時,可見和不可見模擬 reCAPTCHA 應用驗證器的行為不同:

  • Visible reCAPTCHA :當通過appVerifier.render()呈現可見 reCAPTCHA 時,它會在幾分之一秒的延遲後自動解析。這相當於用戶在渲染後立即單擊 reCAPTCHA。 reCAPTCHA 響應將在一段時間後過期,然後再次自動解析。
  • 不可見的 reCAPTCHA :不可見的 reCAPTCHA 不會在呈現時自動解析,而是在appVerifier.verify()調用或在延遲幾分之一秒後單擊 reCAPTCHA 的按鈕錨時自動解析。同樣,響應將在一段時間後過期,並且只會在appVerifier.verify()調用之後或再次單擊 reCAPTCHA 的按鈕錨時自動解析。

每當一個模擬 reCAPTCHA 被解析時,相應的回調函數就會按預期使用假響應觸發。如果還指定了過期回調,它將在過期時觸發。

下一步

用戶首次登錄後,會創建一個新用戶帳戶並將其鏈接到憑據(即用戶名和密碼、電話號碼或身份驗證提供商信息),即用戶登錄時使用的憑據。這個新帳戶作為 Firebase 項目的一部分存儲,可用於在項目中的每個應用中識別用戶,無論用戶如何登錄。

  • 在您的應用程序中,了解用戶身份驗證狀態的推薦方法是在Auth對像上設置觀察者。然後,您可以從User對像中獲取用戶的基本配置文件信息。請參閱管理用戶

  • 在您的 Firebase 實時數據庫和雲存儲安全規則中,您可以從auth變量中獲取登錄用戶的唯一用戶 ID,並使用它來控制用戶可以訪問哪些數據。

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

要註銷用戶,請調用signOut

Web version 9

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

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

Web version 8

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