Dodawanie uwierzytelniania wielopoziomowego TOTP do aplikacji na Androida

Jeśli masz subskrypcję Firebase Authentication with Identity Platform, możesz dodać do aplikacji uwierzytelnianie wielopoziomowe (MFA) z hasłem jednorazowym generowanym na podstawie czasu (TOTP).

Firebase Authentication with Identity Platform umożliwia używanie TOTP jako dodatkowego składnika uwierzytelniania wieloskładnikowego. Gdy włączysz tę funkcję, użytkownicy próbujący zalogować się w Twojej aplikacji zobaczą prośbę o podanie kodu TOTP. Aby go wygenerować, użytkownik musi użyć aplikacji uwierzytelniającej, która potrafi generować prawidłowe kody TOTP, np. Google Authenticator.

Zanim zaczniesz

  1. Włącz co najmniej jednego dostawcę obsługującego MFA. Pamiętaj, że wszyscy dostawcy z wyjątkiem wymienionych poniżej obsługują uwierzytelnianie dwuskładnikowe:

    • Uwierzytelnianie przez telefon
    • Anonimowe uwierzytelnianie
    • Niestandardowe tokeny uwierzytelniania
    • Apple Game Center
  2. Upewnij się, że Twoja aplikacja weryfikuje adresy e-mail użytkowników. MFA wymaga weryfikacji adresu e-mail. Zapobiega to rejestrowaniu się nieuczciwych podmiotów w usłudze za pomocą adresu e-mail, którego nie są właścicielami, a następnie blokowaniu dostępu do tego adresu e-mail przez dodanie drugiego czynnika.

  3. Zainstaluj pakiet SDK Firebase na Androida, jeśli jeszcze tego nie zrobiono.

    Wieloskładnikowe uwierzytelnianie TOTP jest obsługiwane tylko w pakiecie SDK na Androida w wersji 22.1.0 i nowszych.

Włączanie MFA TOTP

Aby włączyć TOTP jako drugi poziom uwierzytelniania, użyj Admin SDK lub wywołaj punkt końcowy REST konfiguracji projektu.

Aby użyć Admin SDK, wykonaj te czynności:

  1. Jeśli jeszcze tego nie zrobiono, zainstaluj pakiet Firebase Admin Node.js SDK.

    Wieloskładnikowe uwierzytelnianie TOTP jest obsługiwane tylko w pakiecie Firebase Admin Node.js SDK w wersji 11.6.0 i nowszych.

  2. Wykonaj zapytanie:

    import { getAuth } from 'firebase-admin/auth';
    
    getAuth().projectConfigManager().updateProjectConfig(
    {
          multiFactorConfig: {
              providerConfigs: [{
                  state: "ENABLED",
                  totpProviderConfig: {
                      adjacentIntervals: NUM_ADJ_INTERVALS
                  }
              }]
          }
    })
    

    Zastąp następujące elementy:

    • NUM_ADJ_INTERVALS: liczba sąsiednich przedziałów czasowych, z których można akceptować kody TOTP. Wartość od zera do dziesięciu. Wartością domyślną jest 5.

      TOTP działają w ten sposób, że gdy 2 strony (weryfikujący i sprawdzający) generują hasła OTP w tym samym przedziale czasu (zwykle 30 sekund), otrzymują to samo hasło. Aby jednak uwzględnić odchylenia zegara między stronami i czas reakcji użytkownika, możesz skonfigurować usługę TOTP tak, aby akceptowała też kody TOTP z sąsiednich okien.

Aby włączyć uwierzytelnianie wieloskładnikowe TOTP za pomocą interfejsu REST API, wykonaj to polecenie:

curl -X PATCH "https://identitytoolkit.googleapis.com/admin/v2/projects/PROJECT_ID/config?updateMask=mfa" \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    -H "Content-Type: application/json" \
    -H "X-Goog-User-Project: PROJECT_ID" \
    -d \
    '{
        "mfa": {
          "providerConfigs": [{
            "state": "ENABLED",
            "totpProviderConfig": {
              "adjacentIntervals": NUM_ADJ_INTERVALS
            }
          }]
       }
    }'

Zastąp następujące elementy:

  • PROJECT_ID: identyfikator projektu.
  • NUM_ADJ_INTERVALS: liczba przedziałów czasowych od zera do dziesięciu. Wartość domyślna to 5.

    TOTP działają w ten sposób, że gdy 2 strony (weryfikujący i sprawdzający) generują hasła OTP w tym samym przedziale czasu (zwykle 30 sekund), otrzymują to samo hasło. Aby jednak uwzględnić odchylenia zegara między stronami i czas reakcji użytkownika, możesz skonfigurować usługę TOTP tak, aby akceptowała też kody TOTP z sąsiednich okien.

Wybierz wzorzec rejestracji

Możesz określić, czy aplikacja wymaga uwierzytelniania wielopoziomowego, a także 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 oferuj opcję pominięcia rejestracji drugiego składnika. Jeśli chcesz zachęcać do uwierzytelniania wielopoziomowego w aplikacji, ale nie chcesz go wymagać, możesz zastosować to podejście.

  • Umożliwia dodanie drugiego czynnika na stronie zarządzania kontem lub profilem użytkownika, a nie na ekranie rejestracji. Minimalizuje to trudności podczas procesu rejestracji, a jednocześnie udostępnia uwierzytelnianie wielopoziomowe użytkownikom, którzy dbają o bezpieczeństwo.

  • Wymagaj stopniowego dodawania drugiego etapu weryfikacji, gdy użytkownik chce uzyskać dostęp do funkcji o zwiększonych wymaganiach dotyczących bezpieczeństwa.

Rejestrowanie użytkowników w MFA opartej na TOTP

Po włączeniu uwierzytelniania wielopoziomowego TOTP jako drugiego poziomu uwierzytelniania w aplikacji zaimplementuj logikę po stronie klienta, aby umożliwić użytkownikom rejestrację w uwierzytelnianiu wielopoziomowym TOTP:

  1. Ponownie uwierzytelnij użytkownika.

  2. Wygeneruj tajny klucz TOTP dla uwierzytelnionego użytkownika:

    // Generate a TOTP secret.
    Firebase.auth.currentUser.multiFactor.session
        .addOnSuccessListener { multiFactorSession ->
            TotpMultiFactorGenerator.generateSecret(multiFactorSession)
                .addOnSuccessListener { totpSecret ->
                    // Display the secret to the user and prompt them to
                    // enter it into their authenticator app. (See the next
                    // step.)
                }
        }
    
  3. Wyświetl tajny klucz użytkownikowi i poproś go o wpisanie go w aplikacji uwierzytelniającej:

    // Display this key:
    val secret = totpSecret.sharedSecretKey
    

    Oprócz wyświetlania klucza tajnego możesz spróbować automatycznie dodać go do domyślnej aplikacji uwierzytelniającej na urządzeniu. Aby to zrobić, wygeneruj adres URI klucza zgodny z aplikacją Google Authenticator i przekaż go do openInOtpApp():

    val qrCodeUri = totpSecret.generateQrCodeUrl(
        currentUser.email ?: "default account",
        "Your App Name")
    totpSecret.openInOtpApp(qrCodeUri)
    

    Gdy użytkownik doda klucz tajny do aplikacji uwierzytelniającej, zacznie ona generować kody TOTP.

  4. Poproś użytkownika o wpisanie kodu TOTP wyświetlanego przez aplikację uwierzytelniającą i użyj go, aby dokończyć rejestrację w systemie MFA:

    // Ask the user for a verification code from the authenticator app.
    val verificationCode = // Code from user input.
    
    // Finalize the enrollment.
    val multiFactorAssertion = TotpMultiFactorGenerator
        .getAssertionForEnrollment(totpSecret, verificationCode)
    Firebase.auth.currentUser.multiFactor.enroll(multiFactorAssertion, "TOTP")
        .addOnSuccessListener {
            // Enrollment complete.
        }
    

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

Aby zalogować użytkowników za pomocą uwierzytelniania wieloskładnikowego TOTP, użyj tego kodu:

  1. Wywołaj jedną z metod signInWith w taki sam sposób, jakby MFA nie była używana. (Na przykład signInWithEmailAndPassword()). Jeśli metoda zgłosi wyjątek FirebaseAuthMultiFactorException, rozpocznij proces uwierzytelniania wieloskładnikowego w aplikacji.

    Firebase.auth.signInWithEmailAndPassword(email, password)
        .addOnSuccessListener { result ->
            // If the user is not enrolled with a second factor and provided valid
            // credentials, sign-in succeeds.
    
            // (If your app requires MFA, this could be considered an error
            // condition, which you would resolve by forcing the user to enroll a
            // second factor.)
    
            // ...
        }
        .addOnFailureListener { exception ->
            when (exception) {
                is FirebaseAuthMultiFactorException -> {
                    // Initiate your second factor sign-in flow. (See next step.)
                    // ...
                }
            }
        }
    
  2. Proces uwierzytelniania dwuskładnikowego w aplikacji powinien najpierw wyświetlić użytkownikowi prośbę o wybranie drugiego składnika, którego chce użyć. Listę obsługiwanych drugich składników możesz uzyskać, sprawdzając właściwość hints instancji MultiFactorResolver:

    val enrolledFactors = exception.resolver.hints.map { it.displayName }
    
  3. Jeśli użytkownik zdecyduje się użyć TOTP, poproś go o wpisanie hasła TOTP wyświetlanego w aplikacji uwierzytelniającej i użycie go do zalogowania się:

    when (exception.resolver.hints[selectedIndex].factorId) {
        TotpMultiFactorGenerator.FACTOR_ID -> {
            val otpFromAuthenticator = // OTP typed by the user.
            val assertion = TotpMultiFactorGenerator.getAssertionForSignIn(
                exception.resolver.hints[selectedIndex].uid,
                otpFromAuthenticator
            )
            exception.resolver.resolveSignIn(assertion)
                .addOnSuccessListener { result ->
                    // Successfully signed in!
                }
                .addOnFailureListener { resolveError ->
                    // Invalid or expired OTP.
                }
        }
        PhoneMultiFactorGenerator.FACTOR_ID -> {
            // Handle SMS second factor.
        }
    }
    

Wyrejestrowanie z MFA TOTP

W tej sekcji opisaliśmy, jak postępować w przypadku, gdy użytkownik wyrejestruje się z wieloskładnikowego uwierzytelniania TOTP.

Jeśli użytkownik zarejestrował się w kilku opcjach MFA i zrezygnuje z najnowszej z nich, otrzyma auth/user-token-expired i zostanie wylogowany. Użytkownik musi ponownie się zalogować i zweryfikować dotychczasowe dane logowania, np. adres e-mail i hasło.

Aby wyrejestrować użytkownika, obsłużyć błąd i ponownie uwierzytelnić, użyj tego kodu:

Firebase.auth.currentUser.multiFactor.unenroll(mfaEnrollmentId)
    .addOnSuccessListener {
        // Second factor unenrolled.
    }
    .addOnFailureListener { exception ->
        when (exception) {
            is FirebaseAuthInvalidUserException -> {
                // Second factor unenrolled. If the user was signed out, re-authenticate
                // them.

                // For example, if they signed in with a password, prompt them to
                // provide it again, then call `reauthenticateWithCredential()` as shown
                // below.
                val credential = EmailAuthProvider.getCredential(email, password)
                currentUser.reauthenticate(credential)
                    .addOnSuccessListener { 
                        // Success!
                    }
                    .addOnFailureListener { 
                        // Bad email address and password combination.
                    }
            }
        }
    }

Co dalej?