在 Flutter 應用程式中新增多重驗證機制

如果您已升級至提供 Identity Platform 的 Firebase 驗證,可以為 Flutter 應用程式新增簡訊多重驗證機制。

多重驗證 (MFA) 可提升應用程式的安全性。攻擊者通常會竊取密碼及盜用社群媒體帳戶,但攔截簡訊則牽涉較為複雜的作業。

事前準備

  1. 啟用至少一個支援多重驗證的供應商。 所有供應商都支援 MFA,電話驗證、匿名驗證和 Apple Game Center 除外。

  2. 確認應用程式會驗證使用者電子郵件。多重驗證需要電子郵件驗證。 這麼做可防止惡意人士使用不屬於自己的電子郵件註冊服務,然後新增第二個驗證因素,將真正的擁有者鎖在門外。

  3. Android:如果您尚未在 Firebase 控制台中設定應用程式的 SHA-256 雜湊,請進行設定。如要瞭解如何找出應用程式的 SHA-256 雜湊,請參閱「驗證用戶端」。

  4. iOS:在 Xcode 中為專案啟用推播通知,並確保 APNs 驗證金鑰已透過 Firebase 雲端通訊 (FCM) 設定。此外,您必須啟用遠端通知的背景模式。如需這個步驟的詳細說明,請參閱 Firebase iOS 電話號碼驗證說明文件。

  5. 網頁:請確認您已在 Firebase 主控台的「OAuth redirect domains」(OAuth 重新導向網域) 下方新增應用程式網域。

啟用多重驗證

  1. 開啟 Firebase 控制台的「驗證」>「登入方式」頁面。

  2. 在「進階」部分,啟用「簡訊多重驗證」

    您也應輸入要用來測試應用程式的電話號碼。 雖然這不是必要步驟,我們仍強烈建議您註冊測試電話號碼,以免在開發期間發生節流情形。

  3. 如果尚未授權應用程式的網域,請在 Firebase 控制台的「驗證」>「設定」頁面中,將該網域新增至允許清單。

選擇註冊模式

您可以選擇應用程式是否需要多重驗證,以及如何和何時註冊使用者。常見的模式包括:

  • 在註冊程序中,為使用者註冊第二個驗證因素。如果應用程式要求所有使用者都必須進行多重驗證,請使用這個方法。

  • 在註冊期間提供可略過的選項,讓使用者註冊第二重驗證。如果應用程式希望使用者啟用多重驗證,但並非強制要求,或許會偏好採用這種做法。

  • 讓使用者在帳戶或個人資料管理頁面新增第二個驗證要素,而非在註冊畫面新增。這樣可盡量減少註冊程序中的阻礙,同時仍為注重安全性的使用者提供多重驗證功能。

  • 當使用者想存取安全性要求較高的功能時,逐步要求新增第二個驗證因素。

註冊第二個驗證步驟

如要為使用者註冊新的次要驗證因素,請按照下列步驟操作:

  1. 重新驗證使用者。

  2. 要求使用者輸入電話號碼。

  3. 取得使用者的多重驗證工作階段:

    final multiFactorSession = await user.multiFactor.getSession();
    
  4. 透過多重驗證工作階段和回呼驗證電話號碼:

    await FirebaseAuth.instance.verifyPhoneNumber(
      multiFactorSession: multiFactorSession,
      phoneNumber: phoneNumber,
      verificationCompleted: (_) {},
      verificationFailed: (_) {},
      codeSent: (String verificationId, int? resendToken) async {
        // The SMS verification code has been sent to the provided phone number.
        // ...
      },
      codeAutoRetrievalTimeout: (_) {},
    );
    
  5. 簡訊驗證碼傳送完畢後,請使用者驗證該驗證碼:

    final credential = PhoneAuthProvider.credential(
      verificationId: verificationId,
      smsCode: smsCode,
    );
    
  6. 完成註冊:

    await user.multiFactor.enroll(
      PhoneMultiFactorGenerator.getAssertion(
        credential,
      ),
    );
    

以下程式碼完整示範如何註冊第二個驗證因素:

  final session = await user.multiFactor.getSession();
  final auth = FirebaseAuth.instance;
  await auth.verifyPhoneNumber(
    multiFactorSession: session,
    phoneNumber: phoneController.text,
    verificationCompleted: (_) {},
    verificationFailed: (_) {},
    codeSent: (String verificationId, int? resendToken) async {
      // See `firebase_auth` example app for a method of retrieving user's sms code:
      // https://github.com/firebase/flutterfire/blob/main/packages/firebase_auth/firebase_auth/example/lib/auth.dart#L591
      final smsCode = await getSmsCodeFromUser(context);

      if (smsCode != null) {
        // Create a PhoneAuthCredential with the code
        final credential = PhoneAuthProvider.credential(
          verificationId: verificationId,
          smsCode: smsCode,
        );

        try {
          await user.multiFactor.enroll(
            PhoneMultiFactorGenerator.getAssertion(
              credential,
            ),
          );
        } on FirebaseAuthException catch (e) {
          print(e.message);
        }
      }
    },
    codeAutoRetrievalTimeout: (_) {},
  );

恭喜!您已成功為使用者註冊第二個驗證因素。

透過第二個驗證要素登入使用者

如要透過簡訊雙重驗證登入使用者,請按照下列步驟操作:

  1. 使用者的第一個驗證因素登入,然後擷取 FirebaseAuthMultiFactorException 例外狀況。這個錯誤包含解析器,可用於取得使用者已註冊的第二個驗證因素。其中也包含基礎工作階段,證明使用者已透過主要驗證方法成功完成驗證。

    舉例來說,如果使用者第一個驗證因素是電子郵件地址和密碼:

    try {
      await _auth.signInWithEmailAndPassword(
          email: emailController.text,
          password: passwordController.text,
      );
      // User is not enrolled with a second factor and is successfully
      // signed in.
      // ...
    } on FirebaseAuthMultiFactorException catch (e) {
      // The user is a multi-factor user. Second factor challenge is required
      final resolver = e.resolver
      // ...
    }
    
  2. 如果使用者已註冊多個次要驗證因素,請詢問要使用哪一個:

    final session = e.resolver.session;
    
    final hint = e.resolver.hints[selectedHint];
    
  3. 將驗證訊息連同提示和多重驗證工作階段傳送至使用者的手機:

    await FirebaseAuth.instance.verifyPhoneNumber(
      multiFactorSession: session,
      multiFactorInfo: hint,
      verificationCompleted: (_) {},
      verificationFailed: (_) {},
      codeSent: (String verificationId, int? resendToken) async {
        // ...
      },
      codeAutoRetrievalTimeout: (_) {},
    );
    
  4. 致電 resolver.resolveSignIn() 完成二次驗證:

    final smsCode = await getSmsCodeFromUser(context);
    if (smsCode != null) {
      // Create a PhoneAuthCredential with the code
      final credential = PhoneAuthProvider.credential(
        verificationId: verificationId,
        smsCode: smsCode,
      );
    
      try {
        await e.resolver.resolveSignIn(
          PhoneMultiFactorGenerator.getAssertion(credential)
        );
      } on FirebaseAuthException catch (e) {
        print(e.message);
      }
    }
    

以下程式碼完整示範如何登入多重驗證使用者:

try {
  await _auth.signInWithEmailAndPassword(
    email: emailController.text,
    password: passwordController.text,
  );
} on FirebaseAuthMultiFactorException catch (e) {
  setState(() {
    error = '${e.message}';
  });
  final firstHint = e.resolver.hints.first;
  if (firstHint is! PhoneMultiFactorInfo) {
    return;
  }
  await FirebaseAuth.instance.verifyPhoneNumber(
    multiFactorSession: e.resolver.session,
    multiFactorInfo: firstHint,
    verificationCompleted: (_) {},
    verificationFailed: (_) {},
    codeSent: (String verificationId, int? resendToken) async {
      // See `firebase_auth` example app for a method of retrieving user's sms code:
      // https://github.com/firebase/flutterfire/blob/main/packages/firebase_auth/firebase_auth/example/lib/auth.dart#L591
      final smsCode = await getSmsCodeFromUser(context);

      if (smsCode != null) {
        // Create a PhoneAuthCredential with the code
        final credential = PhoneAuthProvider.credential(
          verificationId: verificationId,
          smsCode: smsCode,
        );

        try {
          await e.resolver.resolveSignIn(
            PhoneMultiFactorGenerator.getAssertion(
              credential,
            ),
          );
        } on FirebaseAuthException catch (e) {
          print(e.message);
        }
      }
    },
    codeAutoRetrievalTimeout: (_) {},
  );
} catch (e) {
  ...
}

恭喜!您已成功使用多重驗證登入使用者。

後續步驟