MFA(Multi-Factor Authentication)是除了密碼以外,再加一道驗證,例如:
| 第一層 | 第二層 | 常見組合 | 
|---|---|---|
| Email + Password | 簡訊驗證碼(SMS) | 最常見、安全性高 | 
| Google Login | Email 驗證連結 | 輕量級 | 
| 密碼 + Authenticator App | TOTP(如 Google Authenticator) | Firebase 未原生支援,要另外串 | 
| Firebase 目前支援的 MFA 是「SMS 簡訊」或「Authenticator App (Beta)」,我們先從 最易上手的 SMS 版本 實作起。 | 
流程是:使用者登入後 → 進入「帳號安全」頁 → 綁定手機成為第二道防線
import {
  getAuth,
  PhoneAuthProvider,
  multiFactor,
  RecaptchaVerifier,
} from "firebase/auth";
export const enrollMFAWithSMS = async (phoneNumber) => {
  const auth = getAuth();
  const user = auth.currentUser;
  if (!user) throw new Error("尚未登入");
  // 建立 reCAPTCHA 驗證器
  const appVerifier = new RecaptchaVerifier("mfa-recaptcha-container", {}, auth);
  // 使用 PhoneAuthProvider 發送驗證碼
  const provider = new PhoneAuthProvider(auth);
  const verificationId = await provider.verifyPhoneNumber(phoneNumber, appVerifier);
  // 提醒前端等待使用者輸入簡訊驗證碼
  return verificationId; // 讓前端儲存,用於 verify
};
import { PhoneAuthProvider, multiFactor } from "firebase/auth";
export const verifyMFAEnrollment = async (verificationId, smsCode) => {
  const auth = getAuth();
  const user = auth.currentUser;
  if (!user) throw new Error("尚未登入");
  const cred = PhoneAuthProvider.credential(verificationId, smsCode);
  const multiFactorUser = multiFactor(user);
  await multiFactorUser.enroll(cred, "My Phone Number");
  console.log("已成功綁定 MFA");
};
當使用者下一次登入時,Firebase 會自動丟出 錯誤 auth/multi-factor-auth-required,此時你必須再次發簡訊驗證碼來完成登入。
import {
  signInWithEmailAndPassword,
  PhoneAuthProvider,
  RecaptchaVerifier,
} from "firebase/auth";
export const signInWithMFA = async (email, password) => {
  const auth = getAuth();
  try {
    return await signInWithEmailAndPassword(auth, email, password);
  } catch (error) {
    if (error.code === "auth/multi-factor-auth-required") {
      const resolver = error.resolver; // 必須儲存,之後用來完成驗證
      const phoneInfoOptions = resolver.hints[0]; // 預設取第一個綁定的電話
      // 建立 reCAPTCHA 驗證器
      const appVerifier = new RecaptchaVerifier("mfa-recaptcha-container", {}, auth);
      const provider = new PhoneAuthProvider(auth);
      const verificationId = await provider.verifyPhoneNumber(phoneInfoOptions, appVerifier);
      // 回傳資訊,等待使用者輸入 code
      return { resolver, verificationId };
    } else {
      throw error;
    }
  }
};
// 完成登入的第二階段
export const completeMFASignIn = async (resolver, verificationId, smsCode) => {
  const cred = PhoneAuthProvider.credential(verificationId, smsCode);
  const userCredential = await resolver.resolveSignIn(cred);
  console.log("登入成功", userCredential);
  return userCredential;
};