1

PythonのPyJWTライブラリで、AWS CongnitoのJWT(IDトークン)を検証する関数を作った話

5 min read

PyJWTで、AWS CongnitoのJWTを検証する。

こちらの記事を参考にさせていただきました。

PythonでAWS CognitoのJWTのバリデーション + Flaskへの組み込み

WebアプリケーションのバックエンドでDjangoやFlaskに組み込んで使うことを考えているのでひとまずはトークンを引数に、有効なものであるかどうか(True or False)を返す関数を作成しました。

# 前提の環境
!pip install pyjwt==2.1.0
!pip install cryptography==3.4.8

JWTとは?

RFC 7519に定められている。

認証に使う文字列で、簡単に言うと「信頼する機関からの印鑑付き名刺」 ここで信頼する機関とはAWS CognitoやAuth0、Firebase Authenticationなどのサービス

例えばこんな文字列 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

ピリオド(.)で、ヘッダー.ペイロード.署名の3つの部分に分かれている。

これらはBase64 Url decodeすることで中身を確認できる

これはjwt.ioを参照

import jwt

id_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c==="

# まずは中身を見る。
decoded_header = jwt.get_unverified_header(id_token)
decoded_payload = jwt.decode(id_token, options={"verify_signature": False})

print(decoded_header,decoded_payload)
{'alg': 'HS256', 'typ': 'JWT'} {'sub': '1234567890', 'name': 'John Doe', 'iat': 1516239022}

「OAuth & OIDC 入門編 by #authlete」がとてもわかりやすかったです。

https://www.youtube.com/watch?v=PKPj_MmLq5E%E3%80%80

これをCognitoからのJWTトークンでやってみる。

AWS公式、JSON Web トークンの検証

tokenやpoolIDなどは各々のもので試してみてください!

import requests
from jwt.algorithms import RSAAlgorithm
import json

# 公開してはいけません( ;∀;)
token_from_cognito = "eyJraWQiOiJiYkJSS0V6eHZ3WllBXC9xOVwvWjRxOFc3NTBDUHBOM1wvUlBrNVhrQXBXN29jPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIwZGY0MDdhZi1jYjlkLTQ5YjktYjI2MC0xMzFlZGZkNjFlYjIiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLmFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb21cL2FwLW5vcnRoZWFzdC0xXzNmTUJ4cGY1eCIsImNvZ25pdG86dXNlcm5hbWUiOiJuYW95YWhpZWRhIiwib3JpZ2luX2p0aSI6ImY4OTBiYTUwLTc4ZDItNDk4NS05Zjg4LTI4OWVlMWVhNTI4ZiIsImF1ZCI6Ijcwa2JhNmYyMDRwNTVtajk4bzM0a2FiaWY4IiwiZXZlbnRfaWQiOiIyMjJiMmFjMy0zNjFmLTQwYzctYTg1NC01ZDA1ZTgwNTQ0NzEiLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTYzMTg3OTAzOCwiZXhwIjoxNjMxODgyNjM3LCJpYXQiOjE2MzE4NzkwMzgsImp0aSI6IjA5ZmUwMjUzLWJiOGMtNGI4NS04OTVkLWIxZGUzZmQ2MDc2YiIsImVtYWlsIjoibmFveWFoaWVkYUBnbWFpbC5jb20ifQ.kWJsZcogSV6CK8YTP5nGq9q1llc7Try3dfp8haSXWsjIZ2O2L8Q45D796P2i2vnMhhnzLLiQ3zq4qzIDlGA6LqqGQpeNujPwRUVIpdL19QP1EjE6w4MEvBIERohZI27XnQR_3Le2-75MV2DlB6beM64US_qxarnFxHXohDVw_l-_hVv2V4c0vxF3Bun_J90cqb57OKSXE7kygHOGelgI23GiVa13zb69OZHQz2xg-SYaWkXPwn0cdONGVLZleIF5buJ2F15UbPeYAiPT_bi4k9kaw0NCVh4KZSxow-xvehc97cxu0p3uiu4Y5HX6U_cUwFj6Ud9TSMMM-p2cRaZX-Q"


def is_valid(token_from_cognito):
    # ここは各自の変数
    cognito_user_pool_id = "ap-northeast-1_3fMBxpf5x" # 変える
    cognito_app_client_id = "70kba6f204p55mj98o34kabif8" # 変える
    aws_region = "ap-northeast-1"

    # ここからは共通
    cognito_iss = "https://cognito-idp."+ aws_region +".amazonaws.com/" + cognito_user_pool_id
    cognito_jwk_url = cognito_iss + '/.well-known/jwks.json'
    jwk_set = requests.get(cognito_jwk_url).json()

    header = jwt.get_unverified_header(token_from_cognito)
    jwk = next(filter(lambda x: x['kid'] == header['kid'], jwk_set['keys']))
    public_key = RSAAlgorithm.from_jwk(json.dumps(jwk))

    try:
        claims = jwt.decode(
                    token_from_cognito,
                    public_key,
                    issuer=cognito_iss,
                    audience=cognito_app_client_id,
                    algorithms=jwk['alg'],
        )
        print("claimsの中身→",claims)

        print('ユーザーのemail',claims["email"])

        if claims['aud'] != cognito_app_client_id:
            return False
        if claims['iss'] != cognito_iss:
            return False
        if claims['token_use'] != "id":
            return False
        return True
    except Exception as e:
        # だいたい期限切れのエラー
        print("トークンの検証失敗:原因→",str(e))
        return False
        

is_valid(token_from_cognito)

トークンの検証失敗:原因→ Signature has expired

False

Discussion

コメントにはログインが必要です。