Dodawanie uwierzytelniania wielopoziomowego do aplikacji Flutter

Jeśli masz już Uwierzytelnianie Firebase z Identity Platform, możesz dodać do aplikacji na Flutterze uwierzytelnianie wielopoziomowe z użyciem SMS-ów.

Uwierzytelnianie wielopoziomowe (MFA) zwiększa bezpieczeństwo aplikacji. Osoby przeprowadzające ataki często przejmują hasła i konta społecznościowe, ale przechwycenie SMS-a jest dużo trudniejsze.

Zanim zaczniesz

  1. Włącz co najmniej 1 dostawcę, który obsługuje uwierzytelnianie wielopoziomowe. Każdy dostawca obsługuje MFA z wyjątkiem uwierzytelniania przez telefon, uwierzytelniania anonimowego i Apple Game Center.

  2. Sprawdź, czy Twoja aplikacja weryfikuje adresy e-mail użytkowników. MFA wymaga potwierdzania adresu e-mail. Uniemożliwia to złośliwym podmiotom rejestrowanie się w usłudze za pomocą adresu e-mail, którego nie są właścicielami, a następnie blokowanie dostępu prawdziwemu właścicielowi przez dodanie drugiego składnika.

    • iOS+: w Xcode włącz powiadomienia push w projekcie i upewnij się, że klucz uwierzytelniania APNs jest skonfigurowany z Komunikacją w chmurze Firebase (FCM).

      Musisz też włączyć tryby działania w tle w przypadku powiadomień zdalnych. Szczegółowe wyjaśnienie znajdziesz w dokumentacji Firebase iOS Phone Auth.

    • Android: jeśli jeszcze tego nie zrobisz, określ odcisk cyfrowy SHA-256 aplikacji:

      1. W konsoli Firebase otwórz ustawienia Settings > karta General.

      2. Przewiń w dół do karty Your apps (Twoje aplikacje), wybierz aplikację na Androida i dodaj odcisk cyfrowy SHA-256 w polu SHA certificate fingerprints (Odciski cyfrowe certyfikatu SHA).

      Więcej informacji o tym, jak uzyskać odcisk cyfrowy SHA aplikacji, znajdziesz w artykule Uwierzytelnianie klienta.

    • Internet: jeśli jeszcze tego nie zrobisz, autoryzuj domenę aplikacji:

      1. W konsoli Firebase otwórz kartę Security> Authentication > Settings.

      2. W sekcji Authorized domains (Autoryzowane domeny) kliknij Add domain (Dodaj domenę) i dodaj swoją domenę.

Włączanie uwierzytelniania wielopoziomowego

  1. W konsoli Firebase otwórz Security > Authentication.

  2. Na karcie Sign-in method (Metoda logowania) w sekcji Advanced (Zaawansowane) włącz SMS Multi-factor Authentication (Uwierzytelnianie wielopoziomowe z użyciem SMS-ów).

    Wpisz też numery telefonów, za pomocą których będziesz testować aplikację. Chociaż rejestracja numerów telefonów do testów jest opcjonalna, zdecydowanie ją zalecamy, ponieważ pozwoli uniknąć ograniczeń podczas programowania.

  3. Jeśli jeszcze tego nie zrobisz, autoryzuj domenę aplikacji:

    1. W konsoli Firebase otwórz kartę Security (Bezpieczeństwo) > Authentication (Uwierzytelnianie) > Settings (Ustawienia).

    2. W sekcji Authorized domains (Autoryzowane domeny) kliknij Add domain (Dodaj domenę) i dodaj swoją domenę.

Wybieranie wzorca rejestracji

Możesz wybrać, czy Twoja aplikacja wymaga uwierzytelniania wielopoziomowego oraz jak i kiedy rejestrować użytkowników. Oto kilka typowych wzorców:

  • Zarejestruj drugi składnik użytkownika w ramach rejestracji. Użyj tej metody, jeśli Twoja aplikacja wymaga uwierzytelniania wielopoziomowego od wszystkich użytkowników.

  • Podczas rejestracji zaproponuj opcję pominięcia rejestracji drugiego składnika. Aplikacje, które chcą zachęcać do uwierzytelniania wielopoziomowego, ale nie wymagają go, mogą preferować to podejście.

  • Zamiast ekranu rejestracji udostępnij możliwość dodania drugiego składnika na stronie zarządzania kontem lub profilem użytkownika. Minimalizuje to trudności podczas rejestracji, a jednocześnie udostępnia uwierzytelnianie wielopoziomowe użytkownikom, którzy dbają o bezpieczeństwo.

  • Wymagaj dodania drugiego składnika stopniowo, gdy użytkownik chce uzyskać dostęp do funkcji o zwiększonych wymaganiach dotyczących bezpieczeństwa.

Rejestrowanie drugiego składnika

Aby zarejestrować nowy składnik dodatkowy dla użytkownika:

  1. Ponownie uwierzytelnij użytkownika.

  2. Poproś użytkownika o podanie numeru telefonu.

  3. Uzyskaj sesję uwierzytelniania wielopoziomowego dla użytkownika:

    final multiFactorSession = await user.multiFactor.getSession();
    
  4. Zweryfikuj numer telefonu za pomocą sesji uwierzytelniania wielopoziomowego i wywołań zwrotnych:

    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. Gdy SMS zostanie wysłany, poproś użytkownika o zweryfikowanie kodu:

    final credential = PhoneAuthProvider.credential(
      verificationId: verificationId,
      smsCode: smsCode,
    );
    
  6. Dokończ rejestrację:

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

Poniższy kod przedstawia pełny przykład rejestracji drugiego składnika:

  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: (_) {},
  );

Gratulacje! Udało Ci się zarejestrować drugi składnik uwierzytelniania dla użytkownika.

Logowanie użytkowników za pomocą drugiego składnika

Aby zalogować użytkownika za pomocą weryfikacji dwuetapowej przez SMS:

  1. Zaloguj użytkownika za pomocą pierwszego składnika, a następnie przechwyć wyjątek FirebaseAuthMultiFactorException. Ten błąd zawiera resolver, którego możesz użyć do uzyskania zarejestrowanych drugich składników użytkownika. Zawiera też sesję bazową, która potwierdza, że użytkownik pomyślnie uwierzytelnił się za pomocą pierwszego składnika.

    Jeśli na przykład pierwszym składnikiem użytkownika był adres e-mail i hasło:

    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. Jeśli użytkownik ma zarejestrowanych kilka składników dodatkowych, zapytaj go, którego z nich chce użyć:

    final session = e.resolver.session;
    
    final hint = e.resolver.hints[selectedHint];
    
  3. Wyślij na telefon użytkownika wiadomość weryfikacyjną z podpowiedzią i sesją uwierzytelniania wielopoziomowego:

    await FirebaseAuth.instance.verifyPhoneNumber(
      multiFactorSession: session,
      multiFactorInfo: hint,
      verificationCompleted: (_) {},
      verificationFailed: (_) {},
      codeSent: (String verificationId, int? resendToken) async {
        // ...
      },
      codeAutoRetrievalTimeout: (_) {},
    );
    
  4. Aby dokończyć uwierzytelnianie dodatkowe, wywołaj 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);
      }
    }
    

Poniższy kod przedstawia pełny przykład logowania użytkownika z uwierzytelnianiem wielopoziomowym:

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

Gratulacje! Udało Ci się zalogować użytkownika za pomocą uwierzytelniania wielopoziomowego.

Co dalej?