2026/10/02

テクノロジー

Amazon Cognitoで6桁コードの多要素認証(MFA)を試してみた

この記事の目次

    Webサービスのログインをパスワードだけに任せると、パスワードが漏えいした際に第三者がそのままログインできてしまいます。そこで有効な方法の一つが、多要素認証(MFA)です。

    この記事では、Amazon CognitoのユーザープールにTOTP(Time-based One-Time Password)方式のMFAを設定し、認証アプリが生成する6桁コードでログインする流れを、AWS CLI、PowerShell、およびQRコード生成用ライブラリを使って確認します。

    コマンド中のパスワードやTOTPシークレットを、ソースコードやログに残さないでください。

    完成イメージ

    認証フローは次のようになります。

    1. ユーザー名とパスワードを送信する
    2. 初回ログイン時にAmazon CognitoからTOTP設定を要求される
    3. 生成したQRコードを認証アプリで読み取る
    4. 認証アプリの6桁コードをAmazon Cognitoへ送信し、TOTPを登録する
    5. 2回目以降は、パスワード認証後に新しい6桁コードを入力する

    初回のTOTP登録から通常ログインまでの動きは、次のシーケンスになります。

    Amazon CognitoのTOTP MFAは、パスワード認証の後に MFA_SETUP や SOFTWARE_TOKEN_MFA といったチャレンジへ応答する形で進みます。詳しい仕様はAWS公式ドキュメント:TOTP software token MFAで確認できます。

    前提条件

    • AWSアカウントを利用できる
    • AWS CLI v2がインストール済みである
    • aws configure などで認証情報を設定済みである
    • PowerShell 7を利用できる
    • Google Authenticator、Microsoft AuthenticatorなどのTOTP対応アプリを利用できる
    • Amazon Cognitoのユーザープール、アプリクライアント、ユーザーを作成・削除できるIAM権限がある

    最初にAWS CLIが利用できることを確認します。

    aws --version
    aws sts get-caller-identity

    1. 変数を設定する

    状態

    項目状態
    Amazon Cognito検証リソースはまだ作成していない
    検証ユーザーまだ存在しない
    多要素認証(MFA)まだ設定していない

    このハンズオンでは東京リージョンを使用します。ユーザー名やパスワードは必要に応じて変更してください。ただし、このハンズオンのQRコード生成処理をそのまま使用する場合、ユーザー名には英数字を使用してください。

    $env:AWS_DEFAULT_REGION = "ap-northeast-1"
    
    $UserPoolName = "mfa-handson-pool"
    $ClientName   = "mfa-handson-client"
    $Username     = "testuser"
    $Email        = "test@example.com"
    $TemporaryPassword = Read-Host "検証ユーザーの一時パスワード" -MaskInput
    $Password          = Read-Host "検証ユーザーの恒久パスワード" -MaskInput

    入力内容は画面に表示されませんが、AWS CLIの引数として渡すため、この手順は検証環境に限定します。本番ではSDKを利用し、アプリケーション自身が使用するシークレットを保持する必要がある場合は、AWS Secrets ManagerやAWS Systems Manager Parameter StoreのSecureStringなどで管理します。

    • 利用者のパスワード、認証アプリが生成する6桁のTOTPコード、およびTOTPシークレットはログへ出力しない設計としてください。
    • 利用者のパスワードと6桁のTOTPコードは永続保存せず、処理に必要な間だけ一時的に保持してください。TOTPシークレットも、登録処理が完了した後はアプリケーション側で保持しないでください。

    2. Amazon Cognitoのユーザープールを作成する

    状態

    項目状態
    Amazon Cognitoリージョンやリソース名を変数へ設定済み。ユーザープールは未作成
    検証ユーザーまだ存在しない
    多要素認証(MFA)まだ設定していない

    「ユーザープール」とは何か

    ユーザープールは、Webサービスやアプリを利用するユーザーを登録し、サインアップやログインを処理するユーザーディレクトリです。ユーザー情報に加えて、パスワードポリシーやMFAなどの認証設定をまとめて管理し、認証に成功するとアプリケーションへトークンを発行します。

    このハンズオンでは、検証ユーザーとTOTP MFAの設定を格納する認証基盤として使用します。詳細はAWS公式:Amazon Cognito user poolsを参照してください。

    メールアドレスを自動検証属性にし、8文字以上のパスワードを要求するユーザープールを作成します。最低8文字は手順を試しやすくするための検証用設定であり、本番環境の推奨値を示すものではありません。本番では組織のセキュリティポリシーや脅威モデルに合わせて設定してください。

    $result = aws cognito-idp create-user-pool `
      --pool-name $UserPoolName `
      --auto-verified-attributes email `
      --policies '{
        "PasswordPolicy": {
          "MinimumLength": 8,
          "RequireUppercase": true,
          "RequireLowercase": true,
          "RequireNumbers": true,
          "RequireSymbols": false
        }
      }' `
      --account-recovery-setting '{
        "RecoveryMechanisms": [
          {"Name": "verified_email", "Priority": 1}
        ]
      }' `
      --output json
    
    $UserPoolId = ($result | ConvertFrom-Json).UserPool.Id
    $UserPoolId

    3. TOTP MFAを必須にする

    状態

    項目状態
    Amazon Cognitoユーザープールを作成済み
    検証ユーザーまだ存在しない
    多要素認証(MFA)まだ設定していない

    ユーザープールでソフトウェアトークンMFAを有効にし、MFAを必須の ON に設定します。

    aws cognito-idp set-user-pool-mfa-config `
      --user-pool-id $UserPoolId `
      --software-token-mfa-configuration Enabled=true `
      --mfa-configuration ON

    設定を確認します。

    aws cognito-idp get-user-pool-mfa-config `
      --user-pool-id $UserPoolId

    次のように Enabled が true、MfaConfiguration が ON なら成功です。

    {
      "SoftwareTokenMfaConfiguration": {
        "Enabled": true
      },
      "MfaConfiguration": "ON"
    }

    ONにすると、すべてのユーザーはMFAを使用しなければログインを完了できません。MFAをまだ登録していないユーザーは、ログイン中に設定を求められます。ON、OPTIONAL、OFF の違いはAWS公式APIリファレンスに記載されています。

    4. アプリクライアントを作成する

    状態

    項目状態
    Amazon Cognitoユーザープールを作成し、TOTP MFAを必須化済み。アプリクライアントは未作成
    検証ユーザーまだ存在しない
    多要素認証(MFA)ユーザープール側で必須。登録するユーザーはまだいない

    「アプリクライアント」とは何か

    ここでいうアプリクライアントは、スマートフォンへインストールするアプリそのものではありません。Amazon Cognitoを利用するアプリケーションごとにユーザープール内へ作成する、接続・認証設定です。アプリケーションはアプリクライアントIDを指定して、利用する認証方式などをAmazon Cognitoへ伝えます。

    このハンズオンでは、PowerShellとAWS CLIをアプリケーション側として扱います。クライアントシークレットは発行せず、ユーザー名とパスワードによる認証を許可します。また、操作中に期限切れになりにくいよう、認証チャレンジのセッション有効時間を15分にします。詳細はAWS公式:Application-specific settings with app clientsを参照してください。

    $result = aws cognito-idp create-user-pool-client `
      --user-pool-id $UserPoolId `
      --client-name $ClientName `
      --no-generate-secret `
      --explicit-auth-flows ALLOW_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH `
      --auth-session-validity 15 `
      --output json
    
    $ClientId = ($result | ConvertFrom-Json).UserPoolClient.ClientId
    $ClientId

    認証チャレンジ用セッションの有効時間は3~15分です。設定しない場合は短時間でセッションが切れることがあるため、操作しやすいように15分へ延長しています。詳細はAWS CLIの create-user-pool-client リファレンスを参照してください。

    USER_PASSWORD_AUTHは、アプリケーションがユーザーのパスワードをAmazon Cognitoへ直接送信する認証方式です。通信経路はHTTPSで保護されますが、アプリケーションはパスワードを取り扱うため、構成や要件に応じてSRPやManaged loginも検討してください。詳しくはAWS公式:Authentication flowsを参照してください。

    5. 検証ユーザーを作成する

    状態

    項目状態
    Amazon Cognitoユーザープールとアプリクライアントを作成済み
    検証ユーザーまだ存在しない
    多要素認証(MFA)ユーザープール側で必須。登録するユーザーはまだいない

    管理者としてユーザーを作成します。SUPPRESS を指定し、検証用メールアドレスへの招待メール送信を止めます。

    aws cognito-idp admin-create-user `
      --user-pool-id $UserPoolId `
      --username $Username `
      --user-attributes "Name=email,Value=$Email" `
      --temporary-password $TemporaryPassword `
      --message-action SUPPRESS

    続いて、ユーザーのパスワードを恒久パスワードへ変更します。

    aws cognito-idp admin-set-user-password `
      --user-pool-id $UserPoolId `
      --username $Username `
      --password $Password `
      --permanent

    この手順では招待メールを送信せず、メールアドレスの検証も行いません。そのため、設定したメールアドレスは未検証の状態です。今回のハンズオンではTOTP MFAの確認のみを目的とします。

    6. 初回ログインでMFA設定を開始する

    状態

    項目状態
    Amazon Cognitoユーザープールとアプリクライアントを作成済み
    検証ユーザー作成済みで、恒久パスワードも設定済み
    多要素認証(MFA)ユーザーへのTOTP登録はまだ始まっていない

    ユーザー名とパスワードで認証を開始します。

    $AuthParameters = @{
      USERNAME = $Username
      PASSWORD = $Password
    } | ConvertTo-Json -Compress
    
    $auth = aws cognito-idp initiate-auth `
      --auth-flow USER_PASSWORD_AUTH `
      --client-id $ClientId `
      --auth-parameters $AuthParameters `
      --output json
    
    $authObject = $auth | ConvertFrom-Json
    
    [pscustomobject]@{
      ChallengeName = $authObject.ChallengeName
    }

    MFAが必須で、まだTOTPを登録していないため、ChallengeName は MFA_SETUP になります。

    ChallengeName はエラー名ではなく、認証を完了するためにAmazon Cognitoが次に要求している処理を表します。今回の MFA_SETUP は、「ユーザー名とパスワードは確認できたが、必須のMFAが未登録なので、次にTOTPを設定してください」という意味です。この時点では、まだログインは完了していません。

    • MFA_SETUP:認証アプリをまだ登録していないため、TOTPの初期設定が必要
    • SOFTWARE_TOKEN_MFA:登録済みの認証アプリが生成した6桁コードの入力が必要
    {
      "ChallengeName": "MFA_SETUP",
      "Session": "..."
    }

    この Session は認証途中の状態を引き継ぐ重要な値です。後続処理では、各APIが返した最新の Session を次のAPIへ渡します。

    $Session = $authObject.Session

    7. TOTPシークレットを取得する

    状態

    項目状態
    Amazon Cognitoパスワード認証後、MFA_SETUPを要求している
    検証ユーザーパスワードは確認済みだが、ログインはまだ完了していない
    多要素認証(MFA)設定待ち。認証アプリと共有するシークレットは未発行

    TOTPの共有シークレットとは

    共有シークレットは、TOTPの6桁コードを計算するためにAmazon Cognitoがユーザーごとに発行する秘密の文字列です。Amazon Cognitoと認証アプリの両方が同じ値を持つため、「共有」シークレットと呼びます。ユーザーのパスワードや、アプリクライアントのクライアントシークレットとは別のものです。

    この値を知っている第三者も同じ6桁コードを生成できるため、パスワードと同様に秘密情報として扱います。後の章で作成するQRコードにも、この共有シークレットが含まれます。

    associate-software-token を呼び出し、ユーザー固有の共有シークレットを取得します。

    $assoc = aws cognito-idp associate-software-token `
      --session $Session `
      --output json
    
    $assocObject = $assoc | ConvertFrom-Json
    $SecretCode  = $assocObject.SecretCode
    $Session2    = $assocObject.Session

    SecretCodeが、ここで説明したTOTPの共有シークレットです。共有シークレットを平文で画面やログへ出力せず、第三者へ共有しないでください。QRコードにも共有シークレットが含まれるため、取り扱いに注意してください。また、TOTPの登録が完了した後は、アプリケーション側で保持しないでください。AssociateSoftwareToken APIは、このシークレットと次の検証処理に使うセッションを返します。

    8. 認証アプリ用のQRコードを作成する

    状態

    項目状態
    Amazon Cognitoユーザー用のTOTPシークレットと、設定継続用のSessionを発行済み
    検証ユーザーパスワードは確認済みだが、ログインはまだ完了していない
    多要素認証(MFA)シークレットは発行済みだが、認証アプリへの登録とコード検証は未完了

    TOTPアプリが読み取る otpauth:// URIを組み立てます。

    $Issuer = "mfa-handson"
    $OtpUri = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}" -f `
      $Issuer, $Username, $SecretCode

    URIとQRコードを使った実装例はAWS公式:Use AssociateSoftwareToken with an AWS SDK or CLI、TOTPアルゴリズムの仕様はIETF:RFC 6238を参照してください。

    QRコード生成にはQRCoderを使用します。再現性とサプライチェーンリスクを考慮し、最新版ではなく、本記事で動作確認したバージョン 1.7.0 に固定します。この手順ではNuGetパッケージを取得し、SHA-256ハッシュを照合してから展開します。

    $QRCoderVersion = "1.7.0"
    $QRCoderUrl = "https://www.nuget.org/api/v2/package/QRCoder/$QRCoderVersion"
    $QRCoderExpectedHash = `
      "B2CB124014C77F571458D418144109F0F44BB38F3A7220EC5EDAF02FEA336485"
    
    Invoke-WebRequest `
      -Uri $QRCoderUrl `
      -OutFile QRCoder.zip
    
    $QRCoderActualHash = `
      (Get-FileHash -Algorithm SHA256 -LiteralPath ".\QRCoder.zip").Hash
    
    if ($QRCoderActualHash -ne $QRCoderExpectedHash) {
      Remove-Item -LiteralPath ".\QRCoder.zip" -Force
      throw "QRCoder.zipのSHA-256ハッシュが一致しません。処理を中止します。"
    }
    
    Expand-Archive `
      -Path QRCoder.zip `
      -DestinationPath QRCoder `
      -Force

    DLLを読み込み、QRコード画像を生成します。

    Add-Type -Path ".\QRCoder\lib\netstandard2.0\QRCoder.dll"
    
    $qrGenerator = New-Object QRCoder.QRCodeGenerator
    $qrData = $qrGenerator.CreateQrCode(
      $OtpUri,
      [QRCoder.QRCodeGenerator+ECCLevel]::Q
    )
    $qrCode = New-Object QRCoder.PngByteQRCode $qrData
    
    [System.IO.File]::WriteAllBytes(
      (Join-Path $PWD "totp.png"),
      $qrCode.GetGraphic(20)
    )

    生成された totp.png を開き、認証アプリで読み取ります。

    QRコード画像にはTOTPシークレットが含まれます。取り扱いに注意し、検証後は削除してください。

    9. 6桁コードを検証してMFA登録を完了する

    状態

    項目状態
    Amazon CognitoTOTPシークレットを発行済み。認証アプリのコードによる確認待ち
    検証ユーザーパスワードは確認済みだが、ログインはまだ完了していない
    多要素認証(MFA)QRコードを読み取り、TOTPシークレットを認証アプリへ登録済み。ただしAmazon Cognito側の検証は未完了

    認証アプリに表示された6桁コードを入力します。

    $Code = Read-Host "認証アプリに表示された6桁コード"
    
    $verify = aws cognito-idp verify-software-token `
      --session $Session2 `
      --user-code $Code `
      --output json
    
    $verifyObject = $verify | ConvertFrom-Json
    $verifyObject.Status
    $Session3 = $verifyObject.Session

    Status が SUCCESS なら、TOTPコードの検証に成功しています。続いて、最初の MFA_SETUP チャレンジへ応答してログインを完了します。

    $result = aws cognito-idp respond-to-auth-challenge `
      --client-id $ClientId `
      --challenge-name MFA_SETUP `
      --challenge-responses "USERNAME=$Username" `
      --session $Session3 `
      --output json
    
    $authResult = ($result | ConvertFrom-Json).AuthenticationResult
    
    [pscustomobject]@{
      LoginSucceeded = ($null -ne $authResult.AccessToken)
      ExpiresIn       = $authResult.ExpiresIn
      TokenType       = $authResult.TokenType
    }

    LoginSucceeded が True なら、初回ログインは成功です。アクセストークン、IDトークン、リフレッシュトークンは秘密情報のため、値そのものは画面に表示しません。

    VerifySoftwareToken が成功すると、TOTP認証アプリがユーザーへ関連付けられます。続けて、その応答で返されたセッションを使って MFA_SETUP へ応答すると初回ログインが完了します。ここまで成功していれば、追加の管理者操作は必要ありません。AWS公式:VerifySoftwareToken

    10. 通常ログインで多要素認証(MFA)を確認する

    状態

    項目状態
    Amazon CognitoTOTP検証と初回ログインが完了済み
    検証ユーザーCONFIRMED。TOTPの登録が完了し、MFAを使用してログインできる状態
    多要素認証(MFA)登録完了。以降のログインではSOFTWARE_TOKEN_MFAが要求される

    もう一度ユーザー名とパスワードで認証を開始します。

    $auth2 = aws cognito-idp initiate-auth `
      --auth-flow USER_PASSWORD_AUTH `
      --client-id $ClientId `
      --auth-parameters $AuthParameters `
      --output json
    
    $auth2Object = $auth2 | ConvertFrom-Json
    $auth2Object.ChallengeName

    今度は SOFTWARE_TOKEN_MFA が返ります。

    $Session4 = $auth2Object.Session
    $Code2 = Read-Host "認証アプリの新しい6桁コード"
    
    $loginResult = aws cognito-idp respond-to-auth-challenge `
      --client-id $ClientId `
      --challenge-name SOFTWARE_TOKEN_MFA `
      --challenge-responses `
        "USERNAME=$Username,SOFTWARE_TOKEN_MFA_CODE=$Code2" `
      --session $Session4 `
      --output json
    
    $loginAuthResult = ($loginResult | ConvertFrom-Json).AuthenticationResult
    
    [pscustomobject]@{
      LoginSucceeded = ($null -ne $loginAuthResult.AccessToken)
      ExpiresIn       = $loginAuthResult.ExpiresIn
      TokenType       = $loginAuthResult.TokenType
    }

    LoginSucceeded が True なら、パスワードとTOTPを使用したMFAログインに成功しています。ここでもトークンの値は表示しません。

    なぜ認証アプリとAmazon Cognitoの6桁コードが一致するのか

    QRコードを認証アプリで読み取ると、認証アプリは7章でAmazon Cognitoから取得した共有シークレットを取り込みます。これにより、認証アプリとAmazon Cognitoは同じ共有シークレットを持つ状態になります。ログイン時にAmazon Cognitoから認証アプリへ6桁コードを送っているわけではありません。

    認証アプリとAmazon Cognitoは、それぞれが次の情報からTOTPコードを計算します。

    • 登録時に共有したシークレット
    • 現在時刻を30秒単位に区切った値

    両者が同じシークレットとほぼ同じ時刻を使って計算するため、同じ6桁コードになります。利用者が入力したコードを受け取ると、Amazon Cognitoも自身でコードを計算して一致するか確認します。そのため、スマートフォンがオフラインでも認証アプリはコードを生成できます。

    一方、スマートフォンの時刻が大きくずれていたり、別のQRコードを登録していたりすると計算結果は一致しません。Amazon Cognitoは小さな時刻のずれを考慮し、試行時刻の前後30秒以内のコードを受け付けます。詳しい処理はAWS公式:TOTP software token MFAとIETF:RFC 6238を参照してください。

    つまずいたポイント

    SoftwareTokenMFANotFoundException が出る

    ユーザープールでソフトウェアトークンMFAが有効になっているか確認します。

    aws cognito-idp get-user-pool-mfa-config `
      --user-pool-id $UserPoolId

    Invalid session やセッション期限切れになる

    認証フローを途中から再利用せず、「6. 初回ログイン」からやり直してください。また、各コマンドが返した最新の Session を次へ渡しているか確認します。

    CodeMismatchExceptionまたはCode mismatchが出る

    • 認証アプリに表示された最新のコードを入力する
    • PCとスマートフォンの時刻を自動設定にする
    • QRコードを重複登録していないか確認する
    • 6桁コードが切り替わる直前なら、次のコードを待って再試行する

    Amazon Cognitoは時刻のずれを考慮し、試行時刻の前後30秒以内のTOTPを受け付けます。Amazon CognitoがTOTP MFAで受け付けるハッシュ関数はHMAC-SHA1です。詳細はAWS公式のTOTP MFAガイドを参照してください。

    PowerShellでJSONの解釈に失敗する

    PowerShellのバージョンや引用符の扱いで問題になる場合は、JSONを別ファイルへ保存し、AWS CLIの file:// 形式で渡してください。

    後片付け

    検証が終わったら、QRコードとユーザープールを削除します。ユーザープールを削除すると、その配下のユーザーやアプリクライアントも削除されます。

    Remove-Item -LiteralPath ".\totp.png" -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath ".\QRCoder.zip" -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath ".\QRCoder" -Recurse -Force -ErrorAction SilentlyContinue
    
    # 現在のPowerShellセッションに保持した秘密情報を破棄する
    Remove-Variable `
      SecretCode, OtpUri, AuthParameters, `
      TemporaryPassword, Password, Code, Code2, `
      Session, Session2, Session3, Session4, `
      auth, authObject, auth2, assoc, verify, result, loginResult, `
      authResult, loginAuthResult, `
      auth2Object, assocObject, verifyObject, `
      qrData, qrCode, qrGenerator `
      -ErrorAction SilentlyContinue
    
    aws cognito-idp delete-user-pool `
      --user-pool-id $UserPoolId

    検証終了後は、秘密情報がメモリ上に残り続けないよう、PowerShellセッションも終了してください。

    まとめ

    今回は、Amazon Cognitoで次の流れを確認しました。

    • ユーザープールでTOTP MFAを必須化する
    • 初回ログイン時にTOTPシークレットを取得する
    • QRコードを使って認証アプリへ登録する
    • 6桁コードを検証してMFA設定を完了する
    • 通常ログインでパスワードとTOTPの両方を要求する

    APIを直接操作すると、Amazon CognitoのMFAが単一のAPI呼び出しではなく、複数のチャレンジとセッションを引き継ぐ認証フローであることがよく分かります。実際のアプリへ組み込む場合も、ChallengeName を確認しながら適切な画面とAPI処理へ分岐させることがポイントになります。

    補足:ログイン後に発行されるトークン

    ログインに成功すると、Amazon Cognitoは主に次のトークンを発行します。

    • IDトークン:ログインしたユーザーの情報をアプリケーションで確認する
    • アクセストークン:ユーザー向けAPIの呼び出しや、適切に構成された保護対象APIへのアクセス認可に使用する
    • リフレッシュトークン:新しいIDトークンとアクセストークンを取得する

    このハンズオンではAPI認可までは実装しておらず、アクセストークンが返されたことだけを確認してログイン成功と判定しています。トークン値は画面へ表示せず、ブラウザのlocalStorageやCookieにも永続保存していません。

    実際のシステムでは、用途に合ったトークンを選び、署名、有効期限、発行者、対象クライアントなどを検証してから利用します。詳細はAWS公式:ユーザープールのJSON Web Token(JWT)を参照してください。

    参考資料

    ※本記事は2026年10月時点の情報です。

    著者:マイナビエンジニアブログ編集部