了解 2023 年 Google I/O 大会上介绍的 Firebase 亮点。了解详情

將多重身份驗證添加到您的 Web 應用程序

如果您已升級到 Firebase Authentication with Identity Platform,則可以將 SMS 多重身份驗證添加到您的 Web 應用程序。

多重身份驗證可提高應用程序的安全性。雖然攻擊者通常會洩露密碼和社交帳戶,但攔截短信卻更加困難。

在你開始之前

  1. 啟用至少一個支持多重身份驗證的提供商。每個提供商都支持 MFA,電話身份驗證、匿名身份驗證和 Apple Game Center 除外。

  2. 確保您的應用正在驗證用戶電子郵件。 MFA 需要電子郵件驗證。這可以防止惡意行為者使用不屬於他們的電子郵件註冊服務,然後通過添加第二個因素來鎖定真正的所有者。

使用多租戶

如果要在多租戶環境中啟用多重身份驗證,請確保完成以下步驟(除了本文檔中的其餘說明之外):

  1. 在 GCP Console 中,選擇您要使用的租戶。

  2. 在您的代碼中,將Auth實例上的tenantId字段設置為您的租戶 ID。例如:

    網絡模塊化 API

    import { getAuth } from "firebase/auth";
    
    const auth = getAuth(app);
    auth.tenantId = "myTenantId1";
    

    Web 命名空間 API

    firebase.auth().tenantId = 'myTenantId1';
    

啟用多重身份驗證

  1. 打開 Firebase 控制台的身份驗證 > 登錄方法頁面。

  2. Advanced部分,啟用SMS Multi-factor Authentication

    您還應該輸入您將用來測試您的應用程序的電話號碼。雖然是可選的,但強烈建議註冊測試電話號碼以避免在開發過程中受到限制。

  3. 如果您尚未授權應用的域,請將其添加到 Firebase 控制台的“身份驗證”>“設置”頁面上的允許列表。

選擇註冊模式

您可以選擇您的應用程序是否需要多重身份驗證,以及如何以及何時註冊您的用戶。一些常見的模式包括:

  • 註冊用戶的第二因素作為註冊的一部分。如果您的應用程序需要對所有用戶進行多重身份驗證,請使用此方法。

  • 提供可跳過的選項以在註冊期間註冊第二個因素。想要鼓勵但不要求多重身份驗證的應用程序可能更喜歡這種方法。

  • 提供從用戶帳戶或個人資料管理頁面而不是註冊屏幕添加第二因素的能力。這最大限度地減少了註冊過程中的摩擦,同時仍然為對安全敏感的用戶提供多因素身份驗證。

  • 當用戶想要訪問具有更高安全要求的功能時,需要逐漸添加第二個因素。

設置 reCAPTCHA 驗證程序

在發送 SMS 代碼之前,您需要配置 reCAPTCHA 驗證程序。 Firebase 使用 reCAPTCHA 來確保電話號碼驗證請求來自應用允許的域之一,從而防止濫用。

您無需手動設置 reCAPTCHA 客戶端;客戶端 SDK 的RecaptchaVerifier對象自動創建和初始化任何必要的客戶端密鑰和機密。

使用不可見的 reCAPTCHA

RecaptchaVerifier對象支持不可見的 reCAPTCHA ,它通常可以在不需要任何交互的情況下驗證用戶。要使用不可見的 reCAPTCHA,請創建一個size參數設置為invisible RecaptchaVerifier ,並指定啟動多因素註冊的 UI 元素的 ID:

網絡模塊化 API

import { RecaptchaVerifier } from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier("sign-in-button", {
    "size": "invisible",
    "callback": function(response) {
        // reCAPTCHA solved, you can proceed with
        // phoneAuthProvider.verifyPhoneNumber(...).
        onSolvedRecaptcha();
    }
}, auth);

Web 命名空間 API

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
'size': 'invisible',
'callback': function(response) {
  // reCAPTCHA solved, you can proceed with phoneAuthProvider.verifyPhoneNumber(...).
  onSolvedRecaptcha();
}
});

使用 reCAPTCHA 小部件

要使用可見的 reCAPTCHA 小部件,請創建一個 HTML 元素以包含該小部件,然後使用 UI 容器的 ID 創建一個RecaptchaVerifier對象。您還可以選擇設置在 reCAPTCHA 解決或過期時調用的回調:

網絡模塊化 API

import { RecaptchaVerifier } from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier(
    "recaptcha-container",

    // Optional reCAPTCHA parameters.
    {
      "size": "normal",
      "callback": function(response) {
        // reCAPTCHA solved, you can proceed with
        // phoneAuthProvider.verifyPhoneNumber(...).
        onSolvedRecaptcha();
      },
      "expired-callback": function() {
        // Response expired. Ask user to solve reCAPTCHA again.
        // ...
      }
    }, auth
);

Web 命名空間 API

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
  'recaptcha-container',
  // Optional reCAPTCHA parameters.
  {
    'size': 'normal',
    'callback': function(response) {
      // reCAPTCHA solved, you can proceed with phoneAuthProvider.verifyPhoneNumber(...).
      // ...
      onSolvedRecaptcha();
    },
    'expired-callback': function() {
      // Response expired. Ask user to solve reCAPTCHA again.
      // ...
    }
  });

預呈現 reCAPTCHA

或者,您可以在開始雙因素註冊之前預呈現 reCAPTCHA:

網絡模塊化 API

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

Web 命名空間 API

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

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

var recaptchaResponse = grecaptcha.getResponse(window.recaptchaWidgetId);

RecaptchaVerifier 使用verify方法抽像出這個邏輯,所以你不需要直接處理grecaptcha變量。

註冊第二個因素

要為用戶註冊新的次要因素:

  1. 重新驗證用戶。

  2. 要求用戶輸入他們的電話號碼。

  3. 如上一節所述初始化 reCAPTCHA 驗證程序。如果已配置 RecaptchaVerifier 實例,請跳過此步驟:

    網絡模塊化 API

    import { RecaptchaVerifier } from "firebase/auth";
    
    const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
    

    Web 命名空間 API

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. 為用戶獲取多因素會話:

    網絡模塊化 API

    import { multiFactor } from "firebase/auth";
    
    multiFactor(user).getSession().then(function (multiFactorSession) {
        // ...
    });
    

    Web 命名空間 API

    user.multiFactor.getSession().then(function(multiFactorSession) {
      // ...
    })
    
  5. 使用用戶的電話號碼和多因素會話初始化PhoneInfoOptions對象:

    網絡模塊化 API

    // Specify the phone number and pass the MFA session.
    const phoneInfoOptions = {
      phoneNumber: phoneNumber,
      session: multiFactorSession
    };
    

    Web 命名空間 API

    // Specify the phone number and pass the MFA session.
    var phoneInfoOptions = {
      phoneNumber: phoneNumber,
      session: multiFactorSession
    };
    
  6. 向用戶手機發送驗證信息:

    網絡模塊化 API

    import { PhoneAuthProvider } from "firebase/auth";
    
    const phoneAuthProvider = new PhoneAuthProvider(auth);
    phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
        .then(function (verificationId) {
            // verificationId will be needed to complete enrollment.
        });
    

    Web 命名空間 API

    var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
    // Send SMS verification code.
    return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
      .then(function(verificationId) {
        // verificationId will be needed for enrollment completion.
      })
    

    雖然不是必需的,但最好事先通知用戶他們將收到一條 SMS 消息,並且適用標準費率。

  7. 如果請求失敗,請重置 reCAPTCHA,然後重複上一步以便用戶重試。請注意, verifyPhoneNumber()會在拋出錯誤時自動重置 reCAPTCHA,因為 reCAPTCHA 令牌只能一次性使用。

    網絡模塊化 API

    recaptchaVerifier.clear();
    

    Web 命名空間 API

    recaptchaVerifier.clear();
    
  8. 發送短信代碼後,請用戶驗證代碼:

    網絡模塊化 API

    // Ask user for the verification code. Then:
    const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
    

    Web 命名空間 API

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  9. 使用PhoneAuthCredential初始化MultiFactorAssertion對象:

    網絡模塊化 API

    import { PhoneMultiFactorGenerator } from "firebase/auth";
    
    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    Web 命名空間 API

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  10. 完成註冊。或者,您可以為第二個因素指定顯示名稱。這對於具有多個第二因素的用戶很有用,因為電話號碼在身份驗證流程中被屏蔽(例如,+1******1234)。

    網絡模塊化 API

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    multiFactor(user).enroll(multiFactorAssertion, "My personal phone number");
    

    Web 命名空間 API

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user.multiFactor.enroll(multiFactorAssertion, 'My personal phone number');
    

下面的代碼顯示了註冊第二因素的完整示例:

網絡模塊化 API

import {
    multiFactor, PhoneAuthProvider, PhoneMultiFactorGenerator,
    RecaptchaVerifier
} from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
multiFactor(user).getSession()
    .then(function (multiFactorSession) {
        // Specify the phone number and pass the MFA session.
        const phoneInfoOptions = {
            phoneNumber: phoneNumber,
            session: multiFactorSession
        };

        const phoneAuthProvider = new PhoneAuthProvider(auth);

        // Send SMS verification code.
        return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier);
    }).then(function (verificationId) {
        // Ask user for the verification code. Then:
        const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
        const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);

        // Complete enrollment.
        return multiFactor(user).enroll(multiFactorAssertion, mfaDisplayName);
    });

Web 命名空間 API

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
user.multiFactor.getSession().then(function(multiFactorSession) {
  // Specify the phone number and pass the MFA session.
  var phoneInfoOptions = {
    phoneNumber: phoneNumber,
    session: multiFactorSession
  };
  var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
  // Send SMS verification code.
  return phoneAuthProvider.verifyPhoneNumber(
      phoneInfoOptions, recaptchaVerifier);
})
.then(function(verificationId) {
  // Ask user for the verification code.
  var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
  var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
  // Complete enrollment.
  return user.multiFactor.enroll(multiFactorAssertion, mfaDisplayName);
});

恭喜!您已成功為用戶註冊第二個身份驗證因素。

使用第二因素登錄用戶

要使用雙因素短信驗證登錄用戶:

  1. 使用他們的第一個因素讓用戶登錄,然後捕獲auth/multi-factor-auth-required錯誤。此錯誤包含一個解析器、有關註冊的第二個因素的提示,以及一個證明用戶已成功通過第一個因素進行身份驗證的基礎會話。

    例如,如果用戶的第一個因素是電子郵件和密碼:

    網絡模塊化 API

    import { getAuth, getMultiFactorResolver} from "firebase/auth";
    
    const auth = getAuth();
    signInWithEmailAndPassword(auth, email, password)
        .then(function (userCredential) {
            // User successfully signed in and is not enrolled with a second factor.
        })
        .catch(function (error) {
            if (error.code == 'auth/multi-factor-auth-required') {
                // The user is a multi-factor user. Second factor challenge is required.
                resolver = getMultiFactorResolver(auth, error);
                // ...
            } else if (error.code == 'auth/wrong-password') {
                // Handle other errors such as wrong password.
            }
    });
    

    Web 命名空間 API

    firebase.auth().signInWithEmailAndPassword(email, password)
      .then(function(userCredential) {
        // User successfully signed in and is not enrolled with a second factor.
      })
      .catch(function(error) {
        if (error.code == 'auth/multi-factor-auth-required') {
          // The user is a multi-factor user. Second factor challenge is required.
          resolver = error.resolver;
          // ...
        } else if (error.code == 'auth/wrong-password') {
          // Handle other errors such as wrong password.
        } ...
      });
    

    如果用戶的第一個因素是聯合提供者,例如 OAuth、SAML 或 OIDC,請在調用signInWithPopup()signInWithRedirect()後捕獲錯誤。

  2. 如果用戶註冊了多個次要因素,請詢問他們使用哪一個:

    網絡模塊化 API

    // Ask user which second factor to use.
    // You can get the masked phone number via resolver.hints[selectedIndex].phoneNumber
    // You can get the display name via resolver.hints[selectedIndex].displayName
    
    if (resolver.hints[selectedIndex].factorId ===
        PhoneMultiFactorGenerator.FACTOR_ID) {
        // User selected a phone second factor.
        // ...
    } else if (resolver.hints[selectedIndex].factorId ===
               TotpMultiFactorGenerator.FACTOR_ID) {
        // User selected a TOTP second factor.
        // ...
    } else {
        // Unsupported second factor.
    }
    

    Web 命名空間 API

    // Ask user which second factor to use.
    // You can get the masked phone number via resolver.hints[selectedIndex].phoneNumber
    // You can get the display name via resolver.hints[selectedIndex].displayName
    if (resolver.hints[selectedIndex].factorId === firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID) {
      // User selected a phone second factor.
      // ...
    } else if (resolver.hints[selectedIndex].factorId === firebase.auth.TotpMultiFactorGenerator.FACTOR_ID) {
      // User selected a TOTP second factor.
      // ...
    } else {
      // Unsupported second factor.
    }
    
  3. 如上一節所述初始化 reCAPTCHA 驗證器。如果已配置 RecaptchaVerifier 實例,請跳過此步驟:

    網絡模塊化 API

    import { RecaptchaVerifier } from "firebase/auth";
    
    recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
    

    Web 命名空間 API

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. 使用用戶的電話號碼和多因素會話初始化PhoneInfoOptions對象。這些值包含在resolverauth/multi-factor-auth-required錯誤的解析器對像中:

    網絡模塊化 API

    const phoneInfoOptions = {
        multiFactorHint: resolver.hints[selectedIndex],
        session: resolver.session
    };
    

    Web 命名空間 API

    var phoneInfoOptions = {
      multiFactorHint: resolver.hints[selectedIndex],
      session: resolver.session
    };
    
  5. 向用戶手機發送驗證信息:

    網絡模塊化 API

    // Send SMS verification code.
    const phoneAuthProvider = new PhoneAuthProvider(auth);
    phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
        .then(function (verificationId) {
            // verificationId will be needed for sign-in completion.
        });
    

    Web 命名空間 API

    var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
    // Send SMS verification code.
    return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
      .then(function(verificationId) {
        // verificationId will be needed for sign-in completion.
      })
    
  6. 如果請求失敗,請重置 reCAPTCHA,然後重複上一步以便用戶重試:

    網絡模塊化 API

    recaptchaVerifier.clear();
    

    Web 命名空間 API

    recaptchaVerifier.clear();
    
  7. 發送短信代碼後,請用戶驗證代碼:

    網絡模塊化 API

    const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
    

    Web 命名空間 API

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  8. 使用PhoneAuthCredential初始化MultiFactorAssertion對象:

    網絡模塊化 API

    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    Web 命名空間 API

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  9. 調用resolver.resolveSignIn()完成二次認證。然後,您可以訪問原始登錄結果,其中包括標準的提供商特定數據和身份驗證憑據:

    網絡模塊化 API

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(multiFactorAssertion)
        .then(function (userCredential) {
            // userCredential will also contain the user, additionalUserInfo, optional
            // credential (null for email/password) associated with the first factor sign-in.
    
            // For example, if the user signed in with Google as a first factor,
            // userCredential.additionalUserInfo will contain data related to Google
            // provider that the user signed in with.
            // - user.credential contains the Google OAuth credential.
            // - user.credential.accessToken contains the Google OAuth access token.
            // - user.credential.idToken contains the Google OAuth ID token.
        });
    

    Web 命名空間 API

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(multiFactorAssertion)
      .then(function(userCredential) {
        // userCredential will also contain the user, additionalUserInfo, optional
        // credential (null for email/password) associated with the first factor sign-in.
        // For example, if the user signed in with Google as a first factor,
        // userCredential.additionalUserInfo will contain data related to Google provider that
        // the user signed in with.
        // user.credential contains the Google OAuth credential.
        // user.credential.accessToken contains the Google OAuth access token.
        // user.credential.idToken contains the Google OAuth ID token.
      });
    

下面的代碼顯示了登錄多因素用戶的完整示例:

網絡模塊化 API

import {
    getAuth,
    getMultiFactorResolver,
    PhoneAuthProvider,
    PhoneMultiFactorGenerator,
    RecaptchaVerifier,
    signInWithEmailAndPassword
} from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);

const auth = getAuth();
signInWithEmailAndPassword(auth, email, password)
    .then(function (userCredential) {
        // User is not enrolled with a second factor and is successfully
        // signed in.
        // ...
    })
    .catch(function (error) {
        if (error.code == 'auth/multi-factor-auth-required') {
            const resolver = getMultiFactorResolver(auth, error);
            // Ask user which second factor to use.
            if (resolver.hints[selectedIndex].factorId ===
                PhoneMultiFactorGenerator.FACTOR_ID) {
                const phoneInfoOptions = {
                    multiFactorHint: resolver.hints[selectedIndex],
                    session: resolver.session
                };
                const phoneAuthProvider = new PhoneAuthProvider(auth);
                // Send SMS verification code
                return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
                    .then(function (verificationId) {
                        // Ask user for the SMS verification code. Then:
                        const cred = PhoneAuthProvider.credential(
                            verificationId, verificationCode);
                        const multiFactorAssertion =
                            PhoneMultiFactorGenerator.assertion(cred);
                        // Complete sign-in.
                        return resolver.resolveSignIn(multiFactorAssertion)
                    })
                    .then(function (userCredential) {
                        // User successfully signed in with the second factor phone number.
                    });
            } else if (resolver.hints[selectedIndex].factorId ===
                       TotpMultiFactorGenerator.FACTOR_ID) {
                // Handle TOTP MFA.
                // ...
            } else {
                // Unsupported second factor.
            }
        } else if (error.code == 'auth/wrong-password') {
            // Handle other errors such as wrong password.
        }
    });

Web 命名空間 API

var resolver;
firebase.auth().signInWithEmailAndPassword(email, password)
  .then(function(userCredential) {
    // User is not enrolled with a second factor and is successfully signed in.
    // ...
  })
  .catch(function(error) {
    if (error.code == 'auth/multi-factor-auth-required') {
      resolver = error.resolver;
      // Ask user which second factor to use.
      if (resolver.hints[selectedIndex].factorId ===
          firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID) {
        var phoneInfoOptions = {
          multiFactorHint: resolver.hints[selectedIndex],
          session: resolver.session
        };
        var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
        // Send SMS verification code
        return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
          .then(function(verificationId) {
            // Ask user for the SMS verification code.
            var cred = firebase.auth.PhoneAuthProvider.credential(
                verificationId, verificationCode);
            var multiFactorAssertion =
                firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
            // Complete sign-in.
            return resolver.resolveSignIn(multiFactorAssertion)
          })
          .then(function(userCredential) {
            // User successfully signed in with the second factor phone number.
          });
      } else if (resolver.hints[selectedIndex].factorId ===
        firebase.auth.TotpMultiFactorGenerator.FACTOR_ID) {
        // Handle TOTP MFA.
        // ...
      } else {
        // Unsupported second factor.
      }
    } else if (error.code == 'auth/wrong-password') {
      // Handle other errors such as wrong password.
    } ...
  });

恭喜!您已使用多重身份驗證成功登錄用戶。

下一步是什麼