透過 JavaScript 使用電話號碼向 Firebase 驗證

您可以透過 Firebase Authentication 將簡訊傳送至使用者的手機,讓使用者登入。使用者透過簡訊中的一次性代碼登入。

如要輕鬆在應用程式中新增電話號碼登入功能,最簡單的方法是使用 FirebaseUI,其中包含置入式登入小工具,可實作電話號碼登入、密碼登入和聯合登入的登入流程。本文說明如何使用 Firebase SDK 導入電話號碼登入流程。

事前準備

如果您尚未複製初始化程式碼片段,請按照「 將 Firebase 新增至 JavaScript 專案」一文的說明,從 Firebase 控制台將程式碼片段複製到專案中。

安全疑慮

僅使用電話號碼驗證雖然方便,但安全性不如其他可用方法,因為電話號碼很容易在使用者之間轉移。此外,如果裝置有多個使用者設定檔,任何可接收簡訊的使用者都能使用裝置的電話號碼登入帳戶。

如果應用程式採用電話號碼登入方式,請一併提供更安全的登入方式,並告知使用者使用電話號碼登入的安全性考量。

為 Firebase 專案啟用電話號碼登入功能

如要透過簡訊登入使用者,請先為 Firebase 專案啟用電話號碼登入方法:

  1. Firebase 控制台中,開啟「驗證」部分。
  2. 在「登入方式」頁面中,啟用「電話號碼」登入方式。
  3. 選用:在「設定」頁面中,針對要允許或拒絕傳送簡訊的區域設定政策。設定簡訊區域政策有助於保護應用程式,防範簡訊濫用行為。
  4. 在同一頁面中,如果應用程式代管網域未列在「OAuth 重新導向網域」部分,請新增網域。請注意,為了進行電話號碼驗證,系統不允許使用本機主機做為代管網域。

設定 reCAPTCHA 驗證器

您必須先設定 Firebase 的 reCAPTCHA 驗證器,才能讓使用者透過電話號碼登入。Firebase 會使用 reCAPTCHA 防止濫用行為,例如確保電話號碼驗證要求來自應用程式的允許網域。

您不需要手動設定 reCAPTCHA 用戶端;使用 Firebase SDK 的 RecaptchaVerifier 物件時,Firebase 會自動建立及處理所有必要的用戶端金鑰和密鑰。

RecaptchaVerifier 物件支援隱形 reCAPTCHA,通常不需要使用者採取任何動作即可驗證身分,也支援 reCAPTCHA 小工具,但使用者必須與小工具互動才能成功完成驗證。

只要在算繪 reCAPTCHA 前更新 Auth 執行個體上的語言代碼,即可根據使用者偏好設定,將基礎算繪的 reCAPTCHA 本地化。上述本地化作業也會套用至傳送給使用者的簡訊,內含驗證碼。

Web

import { getAuth } from "firebase/auth";

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

Web

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

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

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

Web

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

使用 reCAPTCHA 小工具

如要使用可見的 reCAPTCHA 小工具,請在網頁上建立元素來包含小工具,然後建立 RecaptchaVerifier 物件,並指定容器的 ID。舉例來說:

Web

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

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

Web

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

選用:指定 reCAPTCHA 參數

您可以選擇在 RecaptchaVerifier 物件上設定回呼函式,當使用者解決 reCAPTCHA 驗證問題,或 reCAPTCHA 在使用者提交表單前過期時,系統就會呼叫這些函式:

Web

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

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

Web

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

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

Web

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

render 解析後,您會取得 reCAPTCHA 的小工具 ID,可用於呼叫 reCAPTCHA API:

Web

const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId);

Web

const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId);

將驗證碼傳送至使用者的手機

如要啟動手機號碼登入程序,請向使用者顯示介面,提示他們提供手機號碼,然後呼叫 signInWithPhoneNumber,要求 Firebase 透過簡訊將驗證碼傳送至使用者的手機:

  1. 取得使用者的電話號碼。

    法律規定因國家/地區而異,但為了提供最佳做法並讓使用者瞭解相關資訊,請告知他們如果使用手機登入,可能會收到驗證簡訊,且須支付一般簡訊費用。

  2. 呼叫 signInWithPhoneNumber,並將使用者的電話號碼和先前建立的 RecaptchaVerifier 傳遞給該函式。

    Web

    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

    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 Authentication 將含有驗證碼的簡訊傳送至使用者的手機。

使用驗證碼登入使用者

signInWithPhoneNumber 呼叫成功後,請提示使用者輸入透過簡訊收到的驗證碼。接著,將程式碼傳遞至傳遞給 signInWithPhoneNumber 履行處理常式 (即 then 區塊) 的 ConfirmationResult 物件的 confirm 方法,登入使用者。例如:

Web

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

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 控制台,設定開發用的虛構電話號碼。使用虛構電話號碼進行測試可享有以下好處:

  • 測試電話號碼驗證,不會耗用使用配額。
  • 測試電話號碼驗證,不必傳送實際的簡訊。
  • 使用相同電話號碼連續進行測試,不會受到節流限制。如果審查人員在測試時使用相同的電話號碼,這樣就能盡量避免在應用程式商店審查程序中遭到拒絕。
  • 在開發環境中輕鬆測試,不必費心進行額外作業,例如在 iOS 模擬器或 Android 模擬器中開發,不必使用 Google Play 服務。
  • 編寫整合測試時,不會受到一般在正式環境中對實際電話號碼套用的安全檢查阻礙。

虛構電話號碼必須符合下列規定:

  1. 請務必使用虛構的電話號碼,且該號碼不得已存在。 Firebase Authentication 不允許您將真實使用者使用的現有電話號碼設為測試號碼。 其中一個方法是使用以 555 為前置號碼的美國測試電話號碼,例如: +1 650-555-3434
  2. 電話號碼的長度和其他限制必須符合正確格式。系統仍會對這些號碼進行與一般使用者電話號碼相同的驗證。
  3. 您最多可以新增 10 個開發用電話號碼。
  4. 使用難以猜測的測試電話號碼/代碼,並經常變更。

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

  1. Firebase 控制台中,開啟「驗證」部分。
  2. 在「登入方法」分頁中,啟用電話號碼供應商 (如果尚未啟用)。
  3. 開啟「測試用電話號碼」手風琴選單。
  4. 提供要測試的電話號碼,例如:+1 650-555-3434
  5. 提供該號碼的 6 位數驗證碼,例如:654321
  6. 新增電話號碼。如有需要,只要將游標懸停在對應列上,然後點按垃圾桶圖示,即可刪除電話號碼和驗證碼。

手動測試

您可以在應用程式中直接使用虛構電話號碼。這樣一來,您就能在開發階段執行手動測試,而不必擔心配額問題或節流。您也可以直接從 iOS 模擬器或 Android Emulator 進行測試,不必安裝 Google Play 服務。

提供虛構電話號碼並傳送驗證碼時,系統不會傳送實際的簡訊。您必須提供先前設定的驗證碼,才能完成登入。

登入完成後,系統會使用該電話號碼建立 Firebase 使用者。使用者行為和屬性與實際電話號碼使用者相同,存取 Realtime Database/Cloud Firestore 和其他服務的方式也相同。在此程序中產生的 ID 權杖,與實際電話號碼使用者的簽章相同。

如要進一步限制存取權,也可以透過這些使用者的自訂聲明設定測試角色,將他們區分為虛假使用者。

整合測試

除了手動測試之外,Firebase Authentication 也提供 API,協助您為電話號碼驗證測試編寫整合測試。這些 API 會停用 reCAPTCHA 需求,以及 iOS 中的網頁和無聲推播通知,藉此停用應用程式驗證。因此您可以在這些流程中進行自動化測試,且實作起來也更輕鬆。此外,這些 API 也可協助您在 Android 上測試即時驗證流程。

在網頁上,請先將 appVerificationDisabledForTesting 設為 true,再算繪 firebase.auth.RecaptchaVerifier。這樣系統就會自動解決 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 應用程式驗證器的行為會有所不同:

  • 可見的 reCAPTCHA:透過 appVerifier.render() 轉譯可見的 reCAPTCHA 時,系統會在短暫延遲後自動解決問題。這相當於使用者在 reCAPTCHA 顯示後立即點選。reCAPTCHA 回應會在一段時間後失效,然後再次自動解決。
  • 隱形 reCAPTCHA: 隱形 reCAPTCHA 不會在轉譯時自動解決,而是在 appVerifier.verify() 呼叫時,或在 reCAPTCHA 的按鈕錨點經過一小段延遲後點選時解決。同樣地,回應會在一段時間後失效,且只有在呼叫 appVerifier.verify() 或再次點選 reCAPTCHA 的按鈕錨點時,才會自動解決。

每當模擬 reCAPTCHA 解決時,系統會如預期觸發對應的回呼函式,並提供虛假的回應。如果也指定了到期回呼,則會在到期時觸發。

後續步驟

使用者首次登入後,系統會建立新的使用者帳戶,並連結至使用者登入時使用的憑證 (即使用者名稱和密碼、電話號碼或驗證供應商資訊)。這個新帳戶會儲存在 Firebase 專案中,可用於識別專案中每個應用程式的使用者,無論使用者登入方式為何。

  • 在應用程式中,如要瞭解使用者的驗證狀態,建議在 Auth 物件上設定觀察器。接著,您就可以從 User 物件取得使用者的基本個人資料資訊。請參閱「管理使用者」。

  • Firebase Realtime DatabaseCloud 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.
});