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

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

多重驗證 (MFA) 可提升應用程式的安全性。雖然攻擊者通常會竊取密碼及盜用社群媒體帳戶,但攔截簡訊的難度較高。

事前準備

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

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

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

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

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

啟用多重驗證

  1. 開啟 Firebase 控制台的「Authentication > Sign-in method頁面。

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

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

  3. 如果您尚未授權應用程式的網域,請前往 Firebase 控制台的「Authentication > Settings頁面,將該網域新增至許可清單。

選擇註冊模式

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

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

  • 提供可略過的選項,讓使用者在註冊時註冊第二重驗證。如要鼓勵使用者採用多重驗證,但不強制要求使用者採用,則可能會偏好這種做法。

  • 提供從使用者帳戶或個人資料管理頁面新增第二因素的功能,而非在註冊畫面中提供。這樣一來,註冊程序的摩擦力會降到最低,同時仍可讓重視安全性的使用者使用多重驗證。

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

註冊次要驗證方式

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

  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) {
  ...
}

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

後續步驟