AWS Cognitoのidtokenをdecodeする際にハマったこと

Cognitoアイキャッチ

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ユーザプールの作成とユーザの作成を行います。

Cognitoのマネジメントコンソール画面
図1 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を設定できました。

Cognitoのコンソール画面からnicknameを設定できたことを確認する
図2 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="田中義郎"
Cognitoのコンソール画面からnicknameを設定できたことを確認する
図3nicknameの変更

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テーマ

ランキング

ランキングに参加しています。クリックして応援いただけると嬉しいです。
にほんブログ村 IT技術ブログ クラウドコンピューティングへ
にほんブログ村
AWSランキング
AWSランキング

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次