AWS奮闘記③ ~TOTP認証への変更~

前回はAmazon Cognitoのユーザー作成部分の説明を行ったのだが、
作成時点ではSMSでの認証しか指定できないため、
今回はSMSからTOTPへ変更するやり方を備忘録として記載する。
基本的にはログイン後(SMS認証後)の処理として実行するものとする。

1.シークレットコード取得

import boto3

cognito_idp = boto3.client('cognito-idp')

cognito_user_pool_id = '{ユーザープールID}'
cognito_app_client_id = '{アプリクライアントID}'
cognito_app_client_secret = '{アプリクライアントシークレット}'
user_id = '{任意のユーザーID}'
password = '{任意のパスワード}'

def lambda_handler(event, context):

    # アプリクライアントシークレットによるHash取得
    digest = hmac.new(
        str(cognito_app_client_secret).encode('utf-8'),
        msg = str(user_id + cognito_app_client_id).encode('utf-8'),
        digestmod=hashlib.sha256
    ).digest()
    hash = base64.b64encode(digest).decode()

    # CognitoのSessionを取得
    response = cognito_idp.admin_initiate_auth(
        UserPoolId=cognito_user_pool_id,
        ClientId=cognito_app_client_id,
        AuthFlow='ADMIN_NO_SRP_AUTH',
        AuthParameters={
            'USERNAME': user_id,
            'SECRET_HASH': hash,
            'PASSWORD': password
        }
    )
    session = response['Session']

    # body をパースして、mfa_code を取得
    body = json.loads(event["body"])
    mfa_code = body.get("mfa_code")

    # 認証チャレンジで認証を完了させ、トークンを取得する
    response = cognito_idp.admin_respond_to_auth_challenge(
        UserPoolId=cognito_user_pool_id,
        ClientId=cognito_app_client_id,
        ChallengeName='SOFTWARE_TOKEN_MFA',
        ChallengeResponses={
            'USERNAME': user_id,
            'SECRET_HASH': hash,
            'SOFTWARE_TOKEN_MFA': mfa_code
        },
        Session=session
    )
    # 取得したaccess_tokenをセット
    access_token = response['AuthenticationResult']['AccessToken']

    #シークレットコード取得
    response = cognito_idp.associate_software_token(
        AccessToken=access_token
    )
    secret_code = response['SecretCode']

    return

①アプリクライアントシークレットによるHash取得
 hmac.new(key, msg, digestmod)を使用する。
 key: 秘密鍵を指定する。バイト列 (bytes) である必要がある。
 文字列の場合は、適切なエンコーディング(例: UTF-8)でバイト列に変換する。
 msg: 認証するメッセージを指定。これもバイト列 (bytes) である必要がある。
 digestmod: 使用するハッシュアルゴリズムを指定する。
 hashlib モジュールでサポートされているアルゴリズムの名前(文字列、例: ‘sha256’)を指定するか、
 hashlib.sha256 のようなコンストラクタ/関数オブジェクトを指定する。

②CognitoのSessionを取得
 admin_initiate_authを使用して、Sessionを取得する。
 管理者(サーバー側)が、ユーザー名とパスワードを指定して認証フローを開始する。
 注意点:
 ・AuthFlow(認証フロータイプ)は、「ADMIN_NO_SRP_AUTH」(管理者による通常のユーザー名+パスワード認証)とする。
 ・SECRET_HASHは、Cognitoのアプリクライアント設定に「クライアントシークレット」が有効になっている場合、
 リクエストにSECRET_HASHを含める必要がある。①で取得したhashを設定する。

③bodyをパースして、mfa_code を取得
 eventのbodyより、mfa_codeを取得する。
 API Gateway (HTTP API / REST API) 経由の場合の取得方法を記載。

④認証チャレンジで認証を完了させ、トークンを取得する
 admin_respond_to_auth_challengeを使用し、シークレットコードを取得するためのアクセストークンを取得する。
 注意点:
 ・ChallengeNameは、「SOFTWARE_TOKEN_MFA」を指定する。
 AuthenticatorアプリなどのTOTPコードでMFA認証するためのChallengeNameである。

⑤シークレットコード取得
 associate_software_tokenを使用し、シークレットコードを取得する。
 ④で取得したアクセストークンをセット。

2.MFAコード読み取り時の認証チェック

import boto3

cognito_idp = boto3.client('cognito-idp')

access_token = 1.シークレットコード取得で取得したアクセストークン
mfa_code = Authenticatorアプリなどで表示されるMFAコード

def lambda_handler(event, context):

    # MFA(TOTP)検証を実施
    response = cognito_idp.verify_software_token(
        AccessToken=access_token,
        UserCode=mfa_code
    )
    status = response['Status']

    return

⑥MFA(TOTP)検証を実施
 verify_software_tokenを使用する。
 ユーザーが認証アプリ(例:Google Authenticator など)に表示された
 TOTPコード(ワンタイムパスワード)をサーバー側で検証し、
 MFAデバイス(ソフトウェアトークン)を「確認済み(verified)」状態にマークできる。

技術情報

前の記事

久しぶりのIBMi② DFU