Firebase is back at Google I/O on May 10! Register now

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

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

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

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

在你開始之前

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

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

使用多租戶

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

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

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

    Web version 9

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

    Web version 8

    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參數設置為invisibleRecaptchaVerifier ,並指定啟動多因素註冊的 UI 元素的 ID:

Web version 9

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 version 8

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 解決或過期時調用的回調:

Web version 9

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 version 8

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:

Web version 9

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

Web version 8

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 實例,請跳過此步驟:

    Web version 9

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

    Web version 8

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

    Web version 9

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

    Web version 8

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

    Web version 9

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

    Web version 8

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

    Web version 9

    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 version 8

    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 令牌只能一次性使用。

    Web version 9

    recaptchaVerifier.clear();
    

    Web version 8

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

    Web version 9

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

    Web version 8

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

    Web version 9

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

    Web version 8

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

    Web version 9

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

    Web version 8

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

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

Web version 9

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 version 8

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錯誤。此錯誤包含一個解析器、有關註冊的第二個因素的提示,以及一個證明用戶已成功通過第一個因素進行身份驗證的基礎會話。

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

    Web version 9

    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 version 8

    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. 如果用戶註冊了多個次要因素,請詢問他們使用哪一個:

    Web version 9

    // 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 {
        // Unsupported second factor.
        // Note that only phone second factors are currently supported.
    }
    

    Web version 8

    // 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 {
      // Unsupported second factor.
      // Note that only phone second factors are currently supported.
    }
    
  3. 如上一節所述初始化 reCAPTCHA 驗證器。如果已配置 RecaptchaVerifier 實例,請跳過此步驟:

    Web version 9

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

    Web version 8

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

    Web version 9

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

    Web version 8

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

    Web version 9

    // 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 version 8

    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,然後重複上一步以便用戶重試:

    Web version 9

    recaptchaVerifier.clear();
    

    Web version 8

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

    Web version 9

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

    Web version 8

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

    Web version 9

    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    Web version 8

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

    Web version 9

    // 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 version 8

    // 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 version 9

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 {
                // Unsupported second factor.
            }
        } else if (error.code == 'auth/wrong-password') {
            // Handle other errors such as wrong password.
        }
    });

Web version 8

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 {
        // Unsupported second factor.
      }
    } else if (error.code == 'auth/wrong-password') {
      // Handle other errors such as wrong password.
    } ...
  });

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

下一步是什麼