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

Если вы перешли на 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 Android SDK .

    TOTP MFA поддерживается только в Android SDK версии v22.1.0 и выше.

Включить 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.
    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. Покажите секрет пользователю и предложите ввести его в приложение-аутентификатор:

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

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

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

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

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

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

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

  1. Вызовите один из методов signInWith , как если бы вы не использовали MFA. (Например, signInWithEmailAndPassword() .) Если метод выдает исключение FirebaseAuthMultiFactorException , запустите поток MFA вашего приложения.

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

    val enrolledFactors = exception.resolver.hints.map { it.displayName }
    
  3. Если пользователь выбирает использование TOTP, предложите ему ввести TOTP, отображаемый в приложении для аутентификации, и использовать его для входа:

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

Отписаться от TOTP MFA

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

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

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

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

Что дальше?