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)」状態にマークできる。

