CognitoUserPoolAuthorizer認証で必要になるIDトークン

 以下の記事はアメブロで2021-09-26に投稿したものです。


aws上にREST-APIをpythonで実装する場合、
フレームワークにchalice、認証にCognitoUserPoolAuthorizerとなることも多いと思う。

CognitoUserPoolAuthorizer認証だと、API入り口では、chaliceドキュメントにある通り

from chalice import CognitoUserPoolAuthorizer
authorizer = CognitoUserPoolAuthorizer(
    'MyPool', provider_arns=['arn:aws:cognito:...:userpool/name'])
@app.route('/user-pools', methods=['GET'], authorizer=authorizer)
def authenticated():
    return {"success": True}

というコードになるが、
このAPIをテストしようとcurlやchalice.testでリクエストを投げる際に
IDトークンをAuthorizationリクエストヘッダに設定する必要があるのだ。

このIDトークンを得る方法について、手軽なサンプルがなかったので、おぼえ書としても残しておきたい。
まぁ、こいつを取得するのに面倒な気がしたので、以下に手順を記す。

参考URL
    https://qiita.com/jp_ibis/items/4fffb3c924504f0ce6fb
    https://qiita.com/yakult/items/2cbb2f57c97487b6268b

1.aws_access_key_id、aws_secret_access_keyに紐づいたIAMにcognito関連の権限をつける
(AmazonCognitoPowerUserポリシーとかAdministratorAccessポリシーでOK)

2.AWSコンソールのCognitoでUserPoolを作成する。
(名称は適当、すべてデフォルトで作成、パスワード強度は落とした方が楽)

3.「全般設定」----->「アプリクライアント」----->「アプリクライアントの追加」
    名称は適当でよい
    「クライアントシークレットを生成」------->チェックをはずす、(参考サイトだと)という説明になっているがコード例のようにSECRET_HASHを設定すれば良いだけなので、どちらでもよい
    「ALLOW_USER_PASSWORD_AUTH」------->チェックをつける

4.「全般設定」----->「ユーザーとグループ」----->「ユーザーの作成」
    ユーザー名は適当でよい
    「仮パスワード」------->何か入力する
    「電話番号を検証済みにしますか?」------->チェックをはずす
    「Eメール」------->何か入力する

    ちなみにユーザーの削除は、無効化すると削除ボタンが出てきて可能になる

4.環境変数にACCESS_KEYとSECRET_ACCESS_KEYを設定して以下のコードを実行
このコードはpython、chalice、boto3、pytestがインストールされてる環境からpytest -v --capture=noで実行する

import os
import boto3
import sys
import hmac, hashlib, base64

clientId = アプリクライアントID
username = 作成したユーザー名
password = 設定した仮パスワード
app_client_secretkey = アプリクライアントのシークレット

def new_password_required(aws_client, challengeName, session):
    try:
        secret_hash = create_secret_hash()
        aws_result = aws_client.respond_to_auth_challenge(
            ClientId = clientId,
            ChallengeName = challengeName,
            Session = session,
            ChallengeResponses={
                'USERNAME': username,
                'NEW_PASSWORD': password,
                'SECRET_HASH': secret_hash
            }
        )

        return aws_result
    except Exception as e:
        print('')
        print('###############################')
        print('cognito respond_to_auth_challenge exception')
        print(e)
        print('###############################')
        return None

def create_secret_hash():
    message = bytes(username + clientId, 'utf-8')
    return base64.b64encode(hmac.new(bytes(app_client_secretkey,'utf-8'), message, digestmod=hashlib.sha256).digest()).decode()

def cognito_auth(aws_client):
    try:
        secret_hash = create_secret_hash()
        aws_result = aws_client.initiate_auth(
            ClientId = clientId,
            AuthFlow = "USER_PASSWORD_AUTH",
            AuthParameters = {
                "USERNAME": username,
                "PASSWORD": password,
                'SECRET_HASH': secret_hash
            }
        )

        return aws_result
    except Exception as e:
        print('')
        print('###############################')
        print('cognito auth exception')
        print(e)
        print('###############################')
        return None

def test_get_id_token():
    aws_client = boto3.client('cognito-idp',
        region_name = "ap-northeast-1",
        aws_access_key_id = os.environ["ACCESS_KEY"],
        aws_secret_access_key = os.environ["SECRET_ACCESS_KEY"]
    )

    aws_result = cognito_auth(aws_client)
    if 'ChallengeName' in aws_result.keys() and aws_result['ChallengeName'] == 'NEW_PASSWORD_REQUIRED':
        # ユーザーを作成した直後はパスワード変更を要求されるので、このコードが走る
        aws_result = new_password_required(aws_client, aws_result['ChallengeName'], aws_result['Session'])
        if aws_result is not None:
            aws_result = cognito_auth(aws_client)

    if aws_result is None:
        assert False
        return

    print('')
    print('###############################')
    for key in aws_result.keys():
        print(f'{key} = {aws_result[key]}')
    print('###############################')
    print(aws_result["AuthenticationResult"]["IdToken"])
    print('###############################')

5.IDトークンとして使う
    aws_result["AuthenticationResult"]["IdToken"]

なお、上記コードでは本パスワードも仮パスワードと同じになる。

コメント