Dodawanie uwierzytelniania wielopoziomowego TOTP do aplikacji na iOS

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. Sprawdź, czy 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óry nie należy do nich, a następnie blokowaniu dostępu do tego adresu e-mail przez dodanie drugiego czynnika.

  3. Jeśli jeszcze tego nie zrobiono, zainstaluj pakiet SDK Firebase Apple.

    Wieloskładnikowe uwierzytelnianie TOTP jest obsługiwane tylko w pakiecie Apple SDK w wersji 10.12.0 lub nowszej i tylko w systemie iOS.

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ść domyślna to 5.

      TOTP działają w ten sposób, że gdy 2 strony (weryfikujący i weryfikowany) 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 weryfikowany) 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 etapu weryfikacji 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.
    guard let mfaSession = try? await currentUser.multiFactor.session() else { return }
    guard let totpSecret = try? await TOTPMultiFactorGenerator.generateSecret(with: mfaSession) else { return }
    
    // 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:
    let 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(withQRCodeURL:):

    let otpAuthUri = totpSecret.generateQRCodeURL(
        withAccountName: currentUser.email ?? "default account",
        issuer: "Your App Name")
    totpSecret.openInOTPApp(withQRCodeURL: otpAuthUri)
    

    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.
    let verificationCode = // Code from user input.
    
    // Finalize the enrollment.
    let multiFactorAssertion = TOTPMultiFactorGenerator.assertionForEnrollment(
        with: totpSecret,
        oneTimePassword: verificationCode)
    do {
        try await currentUser.multiFactor.enroll(
            with: multiFactorAssertion,
            displayName: "TOTP")
    } catch {
        // Wrong or expired OTP. Re-prompt the user.
    }
    

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 signIn(with...:), tak jakby nie używać uwierzytelniania wieloskładnikowego (np. signIn(withEmail:password:)). Jeśli metoda zwróci błąd z kodem secondFactorRequired, rozpocznij proces uwierzytelniania wieloskładnikowego w aplikacji.

    do {
        let authResult = try await Auth.auth().signIn(withEmail: email, password: password)
    
        // 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.)
    
        // ...
    } catch let error as AuthErrorCode where error.code == .secondFactorRequired {
        // Initiate your second factor sign-in flow. (See next step.)
        // ...
    } catch {
        // Other auth error.
        throw error
    }
    
  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:

    let mfaKey = AuthErrorUserInfoMultiFactorResolverKey
    guard let resolver = error.userInfo[mfaKey] as? MultiFactorResolver else { return }
    let enrolledFactors = resolver.hints.map(\.displayName)
    
  3. Jeśli użytkownik zdecyduje się użyć TOTP, poproś go o wpisanie kodu TOTP wyświetlanego w aplikacji uwierzytelniającej i użycie go do zalogowania się:

    let multiFactorInfo = resolver.hints[selectedIndex]
    switch multiFactorInfo.factorID {
    case TOTPMultiFactorID:
        let otpFromAuthenticator = // OTP typed by the user.
        let assertion = TOTPMultiFactorGenerator.assertionForSignIn(
            withEnrollmentID: multiFactorInfo.uid,
            oneTimePassword: otpFromAuthenticator)
        do {
            let authResult = try await resolver.resolveSignIn(with: assertion)
        } catch {
            // Wrong or expired OTP. Re-prompt the user.
        }
    default:
        return
    }
    

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:

guard let currentUser = Auth.auth().currentUser else { return }

// Prompt the user to select a factor to unenroll, from this array:
currentUser.multiFactor.enrolledFactors

// ...

// Unenroll the second factor.
let multiFactorInfo = currentUser.multiFactor.enrolledFactors[selectedIndex]
do {
    try await currentUser.multiFactor.unenroll(with: multiFactorInfo)
} catch let error as AuthErrorCode where error.code == .invalidUserToken {
    // Second factor unenrolled, but the user was signed out. Re-authenticate
    // them.
}

Co dalej?