Cognitoで発行されたJWTを検証する

Cognitoユーザープールで発行されたJWTの検証をPythonで行ってみます。 今回はPyJWTというパッケージを利用して検証を行います。
2021.11.10

準備

必要なパッケージをインストールします。

インストール

$ pip3 install pyjwt pyjwt[crypto]

pyjwt[crypto]はJWTの検証に使用する鍵を作成するのに使います。

実装

最終的なコードは以下のようになります。

verify.py

import jwt

token = 'XXXXXXXX'

region = 'ap-northeast-1'
user_pool_id = 'ap-northeast-1_XXXXXXXX'
client_id = 'XXXXXXXX'

issuer = f'https://cognito-idp.{region}.amazonaws.com/{user_pool_id}'
jwks_url = f'{issuer}/.well-known/jwks.json'


jwks_client = jwt.PyJWKClient(jwks_url)
signing_key = jwks_client.get_signing_key_from_jwt(token)

token = jwt.decode(
    token,
    signing_key.key,
    algorithms=["RS256"],
    audience=client_id,
    issuer=issuer
)

if token['token_use'] != 'id':
    raise Exception('Invalid token_use')

流れとしては以下のような感じです。

  • 署名の検証に使用する鍵を作成
  • デコードしつつ検証

以下で順に説明していきます。

今回トークンはAmplifyで取得したものを使用しています。

署名の検証に使用する鍵を作成

鍵の作成

region = 'ap-northeast-1'
user_pool_id = 'ap-northeast-1_XXXXXXXX'
client_id = 'XXXXXXXX'

issuer = f'https://cognito-idp.{region}.amazonaws.com/{user_pool_id}'
jwks_url = f'{issuer}/.well-known/jwks.json'


jwks_client = jwt.PyJWKClient(jwks_url)
signing_key = jwks_client.get_signing_key_from_jwt(token)

ここではJSON Web Key(JWK)を読み込んで署名を検証する用の鍵を作成します。 JWKはユーザープールに紐づいており、jwks_urlから取得できます。

jwks.json

{
  "keys": [
    {
      "alg": "RS256",
      "e": "AQAB",
      "kid": "XXXXXXXX",
      "kty": "RSA",
      "n": "XXXXXXXX",
      "use": "sig"
    },
    {
      "alg": "RS256",
      "e": "AQAB",
      "kid": "XXXXXXXX",
      "kty": "RSA",
      "n": "XXXXXXXX",
      "use": "sig"
    }
  ]
}

JWKは上のような中身になっており、JWTのヘッダーに含まれるkidと一致するようなものがここから選ばれます。

デコードしつつ検証

デコードと検証

token = jwt.decode(
    token, # JWT
    signing_key.key, # 署名を検証するための鍵
    algorithms=["RS256"], # 署名のアルゴリズム
    audience=client_id, # 利用者
    issuer=issuer # トークンの発行者
)

if token['token_use'] != 'id':
    raise Exception('Invalid token_use')

PyJWTではデコードする際に同時に検証ができます。 このパラメータだと以下のクレームを検証してくれます。 検証に失敗するとエラーが発生します。

  • exp
  • iat
  • nbf
  • aud
  • iss

Cognitoのドキュメントによると以下のものを検証するのが推奨みたいです。

  • トークンの有効期限(exp)
  • 利用者(aud)
  • 発行者(iss)
  • トークンの使用用途(token_use)

ここでは上3つまではPyJWTで検証できたので、最後のものは自分で検証します。 今回はIDトークンとして使用しているのでtoken_useidとなることを想定しています。

ここはアクセストークンとして使用するならaccessとなります。

algorithmsについてはハードコードするのが安全みたいです。

デコード後はペイロードがdictとして返されます。

最後に

PyJWTを使用することでJWTの検証を行うことができました。 Flaskアプリケーションなど自分で認証用のコードを書く必要がある時に便利だと思います。