Добавьте многофакторную аутентификацию TOTP в свое приложение iOS.

Если вы обновили Firebase Authentication with Identity Platform , вы можете добавить в свое приложение многофакторную аутентификацию (MFA) с одноразовым паролем на основе времени (TOTP).

Firebase Authentication with Identity Platform позволяет использовать TOTP в качестве дополнительного фактора для MFA. Когда вы включаете эту функцию, пользователи, пытающиеся войти в ваше приложение, видят запрос TOTP. Чтобы сгенерировать его, они должны использовать приложение для аутентификации, способное генерировать действительные коды TOTP, например Google Authenticator .

Прежде чем начать

  1. Включите хотя бы одного поставщика, поддерживающего MFA. Обратите внимание, что все поставщики , кроме следующих, поддерживают MFA:

    • Авторизация по телефону
    • Анонимная авторизация
    • Пользовательские токены аутентификации
    • Apple, игровой центр
  2. Убедитесь, что ваше приложение проверяет адреса электронной почты пользователей. MFA требует подтверждения электронной почты. Это не позволяет злоумышленникам зарегистрироваться в службе с адресом электронной почты, которым они не владеют, а затем заблокировать фактического владельца адреса электронной почты путем добавления второго фактора.

  3. Если вы еще этого не сделали, установите Firebase Apple SDK .

    TOTP MFA поддерживается только в Apple SDK версии 10.12.0 и выше и только в iOS.

Включить TOTP MFA

Чтобы включить TOTP в качестве второго фактора, используйте Admin SDK или вызовите конечную точку REST конфигурации проекта.

Чтобы использовать Admin SDK , выполните следующие действия:

  1. Если вы еще этого не сделали, установите Firebase Admin Node.js SDK .

    TOTP MFA поддерживается только в Firebase Admin Node.js SDK версии 11.6.0 и выше.

  2. Запустите следующее:

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

    Замените следующее:

    • NUM_ADJ_INTERVALS : количество соседних интервалов временного окна, из которых принимаются TOTP, от нуля до десяти. По умолчанию — пять.

      TOTP работают, гарантируя, что когда две стороны (доказывающая и валидатор) генерируют OTP в течение одного и того же временного окна (обычно продолжительностью 30 секунд), они генерируют один и тот же пароль. Однако, чтобы учесть разницу часов между сторонами и время ответа человека, вы можете настроить службу TOTP так, чтобы она также принимала TOTP из соседних окон.

Чтобы включить TOTP MFA с помощью REST API, выполните следующую команду:

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
            }
          }]
       }
    }'

Замените следующее:

  • PROJECT_ID : идентификатор проекта.
  • NUM_ADJ_INTERVALS : количество интервалов временного окна от нуля до десяти. По умолчанию — пять.

    TOTP работают, гарантируя, что когда две стороны (доказывающая и валидатор) генерируют OTP в течение одного и того же временного окна (обычно продолжительностью 30 секунд), они генерируют один и тот же пароль. Однако, чтобы учесть разницу часов между сторонами и время ответа человека, вы можете настроить службу TOTP так, чтобы она также принимала TOTP из соседних окон.

Выберите схему регистрации

Вы можете выбрать, требует ли ваше приложение многофакторную аутентификацию, а также как и когда регистрировать пользователей. Некоторые распространенные шаблоны включают следующее:

  • Зарегистрируйте второй фактор пользователя в рамках регистрации. Используйте этот метод, если ваше приложение требует многофакторной аутентификации для всех пользователей.

  • Предложите возможность пропуска для регистрации второго фактора во время регистрации. Если вы хотите поощрять, но не требовать многофакторную аутентификацию в своем приложении, вы можете использовать этот подход.

  • Предоставьте возможность добавить второй фактор со страницы управления учетной записью или профилем пользователя вместо экрана регистрации. Это сводит к минимуму трудности в процессе регистрации, одновременно делая многофакторную аутентификацию доступной для пользователей, чувствительных к безопасности.

  • Требуйте постепенного добавления второго фактора, когда пользователь хочет получить доступ к функциям с повышенными требованиями безопасности.

Регистрация пользователей в TOTP MFA

После включения TOTP MFA в качестве второго фактора для вашего приложения реализуйте логику на стороне клиента для регистрации пользователей в TOTP MFA:

  1. Повторно авторизуйте пользователя.

  2. Создайте секрет TOTP для аутентифицированного пользователя:

    // 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. Отобразите секрет пользователю и предложите ему ввести его в приложение для аутентификации:

    // Display this key:
    let secret = totpSecret.sharedSecretKey()
    

    Помимо отображения секретного ключа, вы можете попытаться автоматически добавить его в приложение аутентификации устройства по умолчанию. Для этого сгенерируйте URI ключа, совместимый с Google Authenticator , и передайте его openInOTPApp(withQRCodeURL:) :

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

    После того, как пользователь добавит свой секрет в свое приложение для аутентификации, оно начнет генерировать TOTP.

  4. Предложите пользователю ввести TOTP, отображаемый его приложением для проверки подлинности, и использовать его для завершения регистрации 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.
    }
    

Вход пользователей со вторым фактором

Для входа пользователей с помощью TOTP MFA используйте следующий код:

  1. Вызовите один из методов signIn(with...:) - так же, как если бы вы не использовали MFA (например, signIn(withEmail:password:) ). Если метод выдает ошибку с кодом secondFactorRequired , запустите поток MFA вашего приложения.

    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. Поток MFA вашего приложения должен сначала предложить пользователю выбрать второй фактор, который он хочет использовать. Вы можете получить список поддерживаемых вторых факторов, изучив hints экземпляра MultiFactorResolver :

    let mfaKey = AuthErrorUserInfoMultiFactorResolverKey
    guard let resolver = error.userInfo[mfaKey] as? MultiFactorResolver else { return }
    let enrolledFactors = resolver.hints.map(\.displayName)
    
  3. Если пользователь решит использовать TOTP, предложите ему ввести TOTP, отображаемый в его приложении для аутентификации, и использовать его для входа:

    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
    }
    

Отписаться от TOTP МИД

В этом разделе описывается, как обрабатывать отмену регистрации пользователя в TOTP MFA.

Если пользователь подписался на несколько вариантов MFA и отменяет регистрацию от последней включенной опции, он получает сообщение auth/user-token-expired и выходит из системы. Пользователь должен снова войти в систему и подтвердить свои существующие учетные данные, например адрес электронной почты и пароль.

Чтобы отменить регистрацию пользователя, обработать ошибку и запустить повторную аутентификацию, используйте следующий код:

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

Что дальше