Android アプリに TOTP 多要素認証を追加する

Identity Platform を使用する Firebase Authentication にアップグレードした場合は、時間ベースのワンタイム パスワード(TOTP)の多要素認証(MFA)をアプリに追加できます。

Identity Platform を使用する Firebase Authentication では、MFAの追加要素として TOTP を使用できます。この機能を有効にすると、アプリにログインしようとするユーザーに TOTP のリクエストが表示されます。有効な TOTP コードを生成するには、Google 認証システムなど、それを生成できる認証システムアプリを使用する必要があります。

始める前に

  1. MFA をサポートするプロバイダを少なくとも 1 つ有効にします。以下を除くすべてのプロバイダが MFA をサポートしていることにご注意ください。

    • 電話認証
    • 匿名認証
    • カスタム認証トークン
    • Apple Game Center
  2. アプリでユーザーのメールアドレスが検証されるようにします。MFA では、メールの確認を行う必要があります。これにより、悪意のある人物が所有していないメールアドレスでサービスに登録し、2 つ目の要素を追加してそのメールアドレスの実際の所有者をロックアウトすることを防止できます。

  3. まだ Firebase Android SDK をインストールしていない場合は、インストールします。

    TOTP MFA は、Android SDK バージョン v22.1.0 以降でのみサポートされています。

TOTP MFA を有効にする

TOTP を第 2 要素として有効にするには、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 を受け入れる隣接する時間枠間隔の数(0~10)。デフォルト値は 5 です。

      TOTP は、2 つの当事者(証明者と検証者)が同じ時間枠(通常は 30 秒)内に OTP を生成したときに同じパスワードを生成するようにします。ただし、当事者と他のユーザーの応答時間の間に生じるクロック ドリフトに対応するために、隣接する時間枠の TOTP も受け入れるように TOTP サービスを構成できます。

REST API を使用して TOTP MFA を有効にするには、次のコマンドを実行します。

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: プロジェクト ID。
  • NUM_ADJ_INTERVALS: 時間枠間隔の数(0 ~ 10) デフォルト値は 5 です。

    TOTP は、2 つの当事者(証明者と検証者)が同じ時間枠(通常は 30 秒)内に OTP を生成したときに同じパスワードを生成するようにします。ただし、当事者と他のユーザーの応答時間の間に生じるクロック ドリフトに対応するために、隣接する時間枠の TOTP も受け入れるように TOTP サービスを構成できます。

登録パターンを選択する

アプリで多要素認証が必要かどうかと、ユーザーを登録する方法とタイミングを選択できます。一般的なパターンには、次のようなものが含まれます。

  • 登録の一部として、ユーザーの第 2 要素を登録する。アプリがすべてのユーザーに対して多要素認証を必要とする場合は、この方法を使用します。

  • 登録中に第 2 要素の登録をスキップできる選択肢を用意する。アプリで多要素認証を必須とはしないが、推奨する場合は、この方法を使用できます。

  • 登録画面ではなく、ユーザーのアカウントまたはプロフィールの管理ページから第 2 要素を追加する機能を用意する。これにより、登録プロセス中の摩擦が最小限に抑えられる一方、セキュリティに敏感なユーザーは多要素認証を利用できるようになります。

  • セキュリティ要件が強化された機能にユーザーがアクセスする際には、第 2 要素を段階的に追加することを要求する。

TOTP MFA にユーザーを登録する

アプリの第 2 要素として 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 認証システムと互換性のある鍵の 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.
        }
    

第 2 要素を使用してユーザー ログインを行う

TOTP MFA を使用してユーザー ログインを行うには、次のコードを使用します。

  1. MFA を使用していない場合と同様に、signInWith メソッドのいずれかを呼び出します。(例: 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 フローでは、まず、使用する第 2 要素を選択するようユーザーに促します。サポートされている第 2 要素のリストは、MultiFactorResolver インスタンスの hints プロパティを調べて、取得できます。

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

次のステップ