cognitoのidtokenをdecodeするとユーザ名、メールアドレス、電話番号などを取得することができます。
AWS公式 Cognito ID トークンの使用
このidtokenをdecodeする際、誤った実装をしており、特定のidtokenはdecodeできるけど、たまにdecodeに失敗するということがあり気づくのに時間がかかってしまったので記事にします。前提として言語はpythonとさせていただきます。
結論 jwtはurl-safeでdecodeするべき
idtokenのdecodeはjwtに対応しているライブラリ例えばpython-joseを使う。通常のbase64を使ってdecodeするとエラーになる場合がある。
正しいdecodeサンプルコード
from jose import jwt
# python-joseでidtokenをdecode
decoded_payload = jwt.get_unverified_claims(idToken)
RFCによるとjwtは.で区切られたURL-safeだということです。通常のbase64ではurl-safeではないが、python-joseはurl-safeに対応しています。
検証 jwtをbase64とpython-joseでdecodeしてみる
標準のbase64を使うパターンとjwtに対応しているpython-joseを使うパターンで動作を確認していきます。
事前準備 Cognitoにユーザ登録しておく
前準備としてcognitoユーザプールの作成とユーザの作成を行います。
一応admin-set-user-passwordをcliで実行してCONFIRMEDにしておきました。
aws cognito-idp admin-set-user-password --user-pool-id ap-northeast-1_XXXXXXX --username testuser01 --password Password_123 --permanent
ここからはcognitoのどこかの属性に日本を設定してそれぞれのパターンでdecodeできるか検証していきます。
筆者の検証した環境ではjwtに2バイト文字(日本語)が含まれる場合base64でエラーになることがありました。
base64で成功するパターン
nicknameに日本語を設定
nicknameに日本語を設定するために以下コマンドを実行します。設定している「田中太郎」はbase64でdecodeできるパターンです。
aws cognito-idp admin-update-user-attributes --user-pool-id ap-northeast-1_XXXXXXX --username testuser01 --user-attributes Name="nickname",Value="田中太郎"
nicknameを設定できました。
base64でdecodeする
ではbase64でdecodeしてみます。AWS公式 ID トークンの使用によるとペイロードは2つ目なので.で分割して2番目(ソース上は1番目)の要素をdecodeします。base64の特性上、長さが4の倍数になるようにパディングを付与しています。
import boto3
import json
import base64
client = boto3.client('cognito-idp')
response = client.admin_initiate_auth(
UserPoolId="ap-northeast-1_XXXXXX",
ClientId="xxxxxxxxxxxxxxxxxxxx",
AuthFlow="ADMIN_NO_SRP_AUTH",
AuthParameters={
'USERNAME': "testuser01",
'PASSWORD': "Password_123"
}
)
# boto3レスポンスからcognito idtoken取り出し
idToken = response['AuthenticationResult']['IdToken']
# https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-id-token.html
# idtokenには.区切りでheader, payloadが入っている
parse_token = idToken.split('.')
# payloadを取り出し
payload = parse_token[1]
# base64でdocedeするためにパディング符号を付与
payload = payload + '=' * (-len(payload) % 4)
# base64でdecode
decoded_payload = json.loads(base64.b64decode(payload))
# 結果をコンソールに表示
print(decoded_payload)
実行結果は以下の通りとなりました。
{'sub': 'a5cb8186-00ed-4fca-8d5c-8312d1a50b9d',
'email_verified': True,
'iss': 'https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_9ggKJoKvR',
'phone_number_verified': True,
'cognito:username': 'testuser01',
'origin_jti': '8bf6777d-f0e4-47ed-a2f8-56f1303a11c8',
'aud': '5vu97rkrnnp9c5mh975ruat5l1',
'event_id': 'd953651f-6bf9-4d17-86c0-ba31f2960532',
'token_use': 'id',
'auth_time': 1671962942,
'nickname': '田中太郎',
'phone_number': '+819012345678',
'exp': 1671966542,
'iat': 1671962942,
'jti': '2b4cb802-7179-4a49-9496-f0c36c88705d',
'email': 'testuser01@mail.com'}
しっかりdecodeできています。
base64で失敗するパターン
nicknameに日本語を設定(失敗パターン)
では失敗する日本語で試してみます。nicknameを適当に「田中義郎」にしてみます。
aws cognito-idp admin-update-user-attributes --user-pool-id ap-northeast-1_XXXXXX --username testuser01 --user-attributes Name="nickname",Value="田中義郎"
base64でdecodeする
同じコードでdecodeを試みるも下記のようにエラーが発生します。
Traceback (most recent call last):
File "C:\Users\user\work\python\cognitosandbox\base64_cognito_idtoken_decode.py", line 26, in
decoded_payload = json.loads(base64.b64decode(payload))
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.2544.0_x64__qbz5n2kfra8p0\lib\base64.py", line 87, in b64decode
return binascii.a2b_base64(s)
binascii.Error: Incorrect padding
正常にdecodeできる日本語とできない日本語があるようです。
感覚として、難しい漢字はエラーが発生することが多いように感じました。
python-joseでdecodeする
サンプルソース
推奨されているライブラリpython-joseを使用したサンプルコードです。splitせずにidTokenをそのままOSSに渡せばよいのでかなりすっきりしたソースコードになっています。
import boto3
from jose import jwt # pip3 python-joseでインストール
client = boto3.client('cognito-idp')
response = client.admin_initiate_auth(
UserPoolId="ap-northeast-1_XXXXXXX",
ClientId="xxxxxxxxxxxxxxxxxxxxxxx",
AuthFlow="ADMIN_NO_SRP_AUTH",
AuthParameters={
'USERNAME': "testuser01",
'PASSWORD': "Password_123"
}
)
# boto3レスポンスからcognito idtoken取り出し
idToken = response['AuthenticationResult']['IdToken']
# python-joseでidtokenをdecode
decoded_payload = jwt.get_unverified_claims(idToken)
# 結果をコンソールに表示
print(decoded_payload)
実行結果
実行結果です。base64ではdecodeできなかった「田中義郎」でもdecodeできています。
{'sub': 'a5cb8186-00ed-4fca-8d5c-8312d1a50b9d',
'email_verified': True,
'iss': 'https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXX',
'phone_number_verified': True,
'cognito:username': 'testuser01',
'origin_jti': '8069e251-3f43-41d8-9e47-9ae9d7cad089',
'aud': '5vu97rkrnnp9c5mh975ruat5l1',
'event_id': '58a12bab-e35d-4909-afe4-ed83dc1215ab',
'token_use': 'id',
'auth_time': 1671965420,
'nickname': '田中義郎',
'phone_number': '+819012345678',
'exp': 1671969020,
'iat': 1671965420,
'jti': '836e4c1e-f352-4e23-a2b6-ff6899c30e1c',
'email': 'testuser01@mail.com'}
base64とpython-joseの違い
base64はurlsafeではなく、python-joseはurlsafeです。urlsafeのjwtをurlsafeでないbase64でdecodeしようとしたのでうまくdecodeできないことがあったということです。
後述しますが、urlsafeと通常のbase64はエンコードに使用する文字が異なります。
おそらくエラーが出なかったパターンはエンコードした際に偶然urlsafeでのみ利用する「+, /」が使用されなかったということかもしれません。
base64とpython-joseのそれぞれの違いについて解説します。
base64(urlsafeでない)
通常のbase64ではA~Z, a~z, 0~9 までの 62 文字と、記号 +, / の2つとパディング = を使用してエンコードします。base64はメールで画像や日本語を送信するために開発されたエンコード方式です。あらゆる文字記号を64文字に変換します。
python-jose(urlsafe)
上記、通常のbase64だと + や / はURLにしたときに特別な意味を持ってしまいます。そこで + は – に、 / は _ に置き換えてパディングは省略したものです。RFCによるとjwtは.で区切られた「URL-safe」だということです。通常のbase64ではurl-safeではないが、python-joseはurl-safeに対応しています。
A JWT is represented as a sequence of URL-safe parts separated by period (‘.’) characters. Each part contains a base64url-encoded value.
RFC JSON Web Token (JWT) Overview
最後に
jwtに関する知識が不足しており、の原因調査に苦労しました。エラーが出る、出ないがエンコード文字に依存するという発想はなかなか出てきませんでした。
base64にurlsafeとそうでないものがあると知れて勉強になりました。
PR
当ブログはWordPressテーマSWELLを使用しています。非常に使いやすく、簡単にプロのようなデザインを使えるのでお勧めです!!
SWELL – シンプル美と機能性両立を両立させた、圧巻のWordPressテーマ
システムエンジニア
AWSを中心としたクラウド案件に携わっています。
IoTシステムのバックエンド開発、Datadogを用いた監視開発など経験があります。
IT資格マニアでいろいろ取得しています。
AWS認定:SAP, DOP, SAA, DVA, SOA, CLF
Azure認定:AZ-104, AZ-300
ITIL Foundation
Oracle Master Bronze (DBA)
Oracle Master Silver (SQL)
Oracle Java Silver SE
■略歴
理系の大学院を卒業
IT企業に就職
AWSのシステム導入のプロジェクトを担当