PythonからCognitoのUSER_PASSWORD_AUTHとUSER_SRP_AUTHでのトークン取得

Amazon Cognitoの認証フローは複数ありますが、サーバーサイドの処理のパターンから代表的な USER_PASSWORD_AUTHUSER_SRP_AUTH を行う方法を書きます。

AWSの資料から引用した以下の表の〇部分です。

引用元:https://d1.awsstatic.com/webinars/jp/pdf/services/20200630_AWS_BlackBelt_Amazon%20Cognito.pdf

準備

IAM User

curlでの実行でも不要ですが、boto3でも不要です。

CognitoユーザープールID取得

ユーザプールIDをコピーしておきます。

アプリケーションクライアント作成

Cognitoユーザプール > アプリケーション統合 > アプリケーションクライアントの作成から、アプリケーションクライアントを作成します。

ブログ用に一時的に作った以下のクライアントを使います。

シークレット有り版も作っておきます。

USER_PASSWORD_AUTH

ユーザとパスワードを送ることで認証するシンプルなパターンです。

curl

curlで実行する場合は以下のようにリクエスト送ると、

REGION=ap-northeast-1
USER=yusuke.otomo@dummy.co.jp
PASSWORD=P@ssw0rd
CLIENT_ID=52tr3abq5hnj7ui6mkidadedcq

curl -XPOST -H 'Content-Type: application/x-amz-json-1.1' \
  -H 'X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth' \
  -d "{\"AuthFlow\": \"USER_PASSWORD_AUTH\", \"AuthParameters\": {\"USERNAME\": \"${USER}\", \"PASSWORD\": \"${PASSWORD}\"}, \"ClientId\": \"${CLIENT_ID}\"}" \
  https://cognito-idp.$REGION.amazonaws.com

以下のようにトークン取得できます。

{
  "AuthenticationResult": {
    "AccessToken": "eyJ...",
    "ExpiresIn": 3600,
    "IdToken": "eyJ...",
    "RefreshToken": "eyJ...",
    "TokenType": "Bearer"
  },
  "ChallengeParameters": {}
}

Python

import boto3
from pprint import pprint

# IAM権限は不要です。空権限のユーザ・ロールでもOKv
cognito_cli = boto3.client("cognito-idp",region_name="ap-northeast-1")

client_id = "52tr3abq5hnj7ui6mkidadedcq"
username = "yusuke.otomo@dummy.co.jp"
password = "P@ssw0rd"
user_pool_id = "ap-northeast-1_Txxxxx" # 保存しておいたユーザプールID

res = cognito_cli.initiate_auth(
    AuthFlow='USER_PASSWORD_AUTH',
    AuthParameters={
        'USERNAME': username,
        'PASSWORD': password
    },
    ClientId=client_id,
)
pprint(res["AuthenticationResult"])

トークン取得できました。

{'AccessToken': 'eyJ...',
 'ExpiresIn': 3600,
 'IdToken': 'eyJ...',
 'RefreshToken': 'eyJ...',
 'TokenType': 'Bearer'}

Python(クライアントシークレット対応)

クライアントシークレットに対応する場合は、SECRET_HASH の計算が必要です。

import boto3
import base64,hmac,hashlib
from pprint import pprint

# IAM権限は不要です
cognito_cli = boto3.client("cognito-idp",region_name="ap-northeast-1")

user_pool_id = "ap-northeast-1_Txxxxx" 
client_id = "6d22r9jmslov5ko4megustei8k"
client_secret = "1jpflajn8hu40meuidgi9vg4tm89n9hktmueltgibiscbeq2dvqk"
username = "yusuke.otomo@dummy.co.jp"
password = "P@ssw0rd"

# SECRET_HASH計算
message = bytes(username+client_id,'utf-8')
key = bytes(client_secret,'utf-8')
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()

response = cognito_cli.initiate_auth(
    AuthFlow='USER_PASSWORD_AUTH',
    AuthParameters={
        'USERNAME': "yusuke.otomo@dummy.com",
        'PASSWORD': "P@ssw0rd",
        "SECRET_HASH": secret_hash,
    },
    ClientId=client_id,
)
pprint(res["AuthenticationResult"])

USER_SRP_AUTH

USER_SRP_AUTH はパスワードを直接サーバーに送らないセキュアな方式で、AWSからも推奨されています。RFCで規定されている標準的な手法です。

  • RFC 2945 - The SRP Authentication and Key Exchange System
  • RFC 5054 - Using the Secure Remote Password (SRP) Protocol for TLS Authentication

引用元:https://d1.awsstatic.com/webinars/jp/pdf/services/20200630_AWS_BlackBelt_Amazon%20Cognito.pdf

動きを知りたい場合はRFPを読むか、以下のサイトが日本語で書かれていてわかりやすいです。

Secure Remote Password (SRP) Protocol

実装検討時の参考

実装する時参考になるのがAmplifyのコードです。

github.com

アプリケーションクライアント設定

ALLOW_USER_SRP_AUTH を認証フローに追加しておきます。

Python

Pythonで上記のものを実装した内容が紹介されているのが以下の記事です。上記のSRPの仕様見ながら追ってみると面白いです。

zenn.dev

とは言え内容見るとわかる通り、実装するのはそれなりに面倒です。。。

Pythonなら以下のライブラリを利用可能です。

github.com

pipでインストールして利用します。

pip install warrant

以下のように簡単にSRPが利用できるようになります。

import boto3
from warrant.aws_srp import AWSSRP

# IAM権限は不要です。空権限のユーザ・ロールでもOK
cognito_cli = boto3.client("cognito-idp", region_name="ap-northeast-1")

client_id = "52tr3abq5hnj7ui6mkidadedcq"
username = "yusuke.otomo@dummy.com"
password = "P@ssw0rd"
user_pool_id = "ap-northeast-xxxxxxxxx"

aws_srp = AWSSRP(
    username=username,
    password=password,
    pool_id=user_pool_id,
    client_id=client_id,
    client=cognito_cli,
)

tokens = aws_srp.authenticate_user()
pprint(tokens["AuthenticationResult"])

トークン取得できました。

{'AccessToken': 'eyJ...',
 'ExpiresIn': 3600,
 'IdToken': 'eyJ...',
 'RefreshToken': 'eyJ...',
 'TokenType': 'Bearer'}

ちなみに2022/08/09時点ではシークレット付きのアプリケーションクライアントに対するSRPは問題が出ているようで、利用できませんでした。これですが、PRまで出ているようなので中身見て対応も可能だと思います。

github.com

参考URL

docs.aws.amazon.com

Amazon Cognito の「クライアントのシークレットのハッシュを検証できません」というエラーを解決する