ALB の x-amzn-oidc-accesstoken で API Gateway Cognito オーソライザーの認可を通す
背景
ALBとCognitoを連携した認証から払い出される情報を用いて、API GatewayのCognitoオーソライザーの認可を通したい。
しかし認証のフローから、ALBとCognitoを連携して認証を行うと、認可コードは発行されるが、ALBが認可コードを握りつぶしてx-amzn-oidc-accesstoken, x-amzn-oidc-identity, x-amzn-oidc-dataをターゲットグループへヘッダーとして返却する。この場合、ターゲットグループ側では認可コードを利用してID Tokenを発行できないため、ターゲットグループからAPI GatewayのCognitoオーソライザーをID Tokenを使用して認証することができない。
ここでx-amzn-oidc-dataは、ユーザークレームのエンコードと署名の検証からhttps://public-keys.auth.elb.region.amazonaws.com/key-idの公開鍵で検証できる。しかし、Cognitoの公開鍵(https://cognito-idp.<Region>.amazonaws.com/<userPoolId>/.well-known/jwks.json)とは異なるため、Cognitoオーソライザーには使用できないことが分かる。
一方x-amzn-oidc-accesstokenについては、Cognitoの公開鍵で検証できるため、これをCognitoのアクセストークンとしてCognitoオーソライザーの認可に使えないかと考えた。結果として、x-amzn-oidc-accesstokenのscopeを設定することでCognitoオーソライザーの認可を通すことができたので、その方法をメモしておく。
AWSのインフラ構成
以下の構成で検証を行う。はじめに、example.comでALB経由でFargateへアクセスし、そこで払い出されるx-amzn-oidc-accesstoken, x-amzn-oidc-identity, x-amzn-oidc-dataを取得する。そして、そのトークンを公開鍵で検証し、ユーザー属性エンドポイントとAPI Gateway Cognitoオーソライザーへアクセスできるかテストする。
room-208/alb-accesstoken-cognito-authorizerを使用してAWSのインフラを立ち上げる。使い方はリポジトリのAWS CDKの立ち上げを確認してください。

カスタムスコープの設定
リソースサーバーを使用したスコープ、M2M、および APIやAmazon Cognito ユーザープールをオーソライザーとして使用して REST API へのアクセスを制御するから、アクセストークンを用いてCognitoオーソライザーの認可を得るためにはカスタムスコープの設定が必要であることが分かる。
この章では、x-amzn-oidc-accesstokenを使用してCognitoオーソライザーの認可を得るために必要な設定をメモする。
Cognitoの設定
Cognitoでは、まずリソースサーバーを作成し、それに対応するスコープを定義する必要がある。

その後、Cognitoのアプリケーションクライアントへ払い出すカスタムスコープを追加する。

以上の設定で、このアプリケーションクライアントの認証を受けたユーザーがカスタムスコープの権限を取得できる。つまり、以下のようにアクセストークンのペイロードのscopeへ、カスタムスコープが追加されるということである。
{
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX",
"version": 2,
"client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"origin_jti": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"token_use": "access",
"scope": "openid example.com/api:all",
"auth_time": 1759083604,
"exp": 1759087204,
"iat": 1759083604,
"jti": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
ALBの設定
ALBが認可コードを使用してトークンエンドポイントへアクセスし、アクセストークンを払い出すため、ALBがどのスコープを要求するか事前に設定する必要がある。そのため以下のように、ALBのリスナールールのCognito認証において、カスタムスコープを定義する必要がある。

これによって、以下のようにx-amzn-oidc-accesstokenのペイロードのscopeへ、カスタムスコープが追加される。
{
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX",
"version": 2,
"client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"origin_jti": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"event_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"token_use": "access",
"scope": "openid example.com/api:all",
"auth_time": 1759083358,
"exp": 1759086958,
"iat": 1759083358,
"jti": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
API Gatewayの設定
API Gatewayにリクエストされるアクセストークンとして、どのカスタムスコープを持っていた場合にアクセス許可を出すか設定する必要がある。そのため以下のように、REST APIの認可スコープへ、カスタムスコープを設定する必要がある。

トークンのテスト
x-amzn-oidc-accesstoken, x-amzn-oidc-dataの検証とアクセス確認を行う。具体的なテスト方法はリポジトリのトークンのテストを確認してください。
トークンの公開鍵を用いた検証
以下のalb-accesstoken-cognito-authorizer/python/01_test_token.pyを用いて、アクセストークンを公開鍵で検証できる。結果としては、改ざんがなくデコードできることを確認できた。
alb-accesstoken-cognito-authorizer/python/01_test_token.py
import os
import dotenv
import jwt
import requests
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from jwt import PyJWKClient
dotenv.load_dotenv()
REGION = os.environ["REGION"]
COGNITO_USER_POOL_ID = os.environ["COGNITO_USER_POOL_ID"]
HTTP_X_AMZN_OIDC_DATA = os.environ["HTTP_X_AMZN_OIDC_DATA"]
HTTP_X_AMZN_OIDC_ACCESSTOKEN = os.environ["HTTP_X_AMZN_OIDC_ACCESSTOKEN"]
alb_public_key_base_url = f"https://public-keys.auth.elb.{REGION}.amazonaws.com"
cognito_public_key_url = f"https://cognito-idp.{REGION}.amazonaws.com/{COGNITO_USER_POOL_ID}/.well-known/jwks.json"
# verify x-amzn-oidc-data
alb_header = jwt.get_unverified_header(HTTP_X_AMZN_OIDC_DATA)
alb_kid = alb_header["kid"]
alb_public_key_url = f"{alb_public_key_base_url}/{alb_kid}"
alb_pem = requests.get(alb_public_key_url).text
alb_public_key = serialization.load_pem_public_key(
alb_pem.encode(), backend=default_backend()
)
alb_payload = jwt.decode(HTTP_X_AMZN_OIDC_DATA, alb_public_key, algorithms=["ES256"])
print(alb_payload)
# {
# "sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# "email_verified": "true",
# "email": "example@example.com",
# "username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# "exp": 1759073152,
# "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX",
# }
# verify x-amzn-oidc-accesstoken
cognito_jwk_client = PyJWKClient(cognito_public_key_url)
cognito_signing_key = cognito_jwk_client.get_signing_key_from_jwt(
HTTP_X_AMZN_OIDC_ACCESSTOKEN
)
cognito_payload = jwt.decode(
HTTP_X_AMZN_OIDC_ACCESSTOKEN, cognito_signing_key.key, algorithms=["RS256"]
)
print(cognito_payload)
# {
# "sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX",
# "version": 2,
# "client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
# "origin_jti": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# "event_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# "token_use": "access",
# "scope": "openid api.example.com/api:all",
# "auth_time": 1759073032,
# "exp": 1759076632,
# "iat": 1759073032,
# "jti": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# "username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# }
ユーザー属性エンドポイントへのアクセス確認
以下のalb-accesstoken-cognito-authorizer/python/02_test_user_info.pyを用いて、アクセストークンを用いてユーザー属性エンドポイントへアクセスできるかテストできる。結果としては、ユーザー属性エンドポイントへアクセスできることを確認できた。
alb-accesstoken-cognito-authorizer/python/02_test_user_info.py
import os
import dotenv
import requests
dotenv.load_dotenv()
TOKEN = os.environ["HTTP_X_AMZN_OIDC_ACCESSTOKEN"]
COGNITO_DOMAIN = os.environ["COGNITO_DOMAIN"]
headers = {"Authorization": f"Bearer {TOKEN}"}
response = requests.get(f"https://{COGNITO_DOMAIN}/oauth2/userInfo", headers=headers)
print(response.text)
# {
# "sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# "email_verified": "true",
# "email": "example@example.com",
# "username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# }
APIへのアクセス確認
以下のalb-accesstoken-cognito-authorizer/python/03_test_api_auth.pyを用いて、アクセストークンを用いてAPIへアクセスできるかテストできる。結果としては、API GatewayのCognitoオーソライザーの認可へ使用できることを確認できた。
alb-accesstoken-cognito-authorizer/python/03_test_api_auth.py
import os
import dotenv
import requests
dotenv.load_dotenv()
TOKEN = os.environ["HTTP_X_AMZN_OIDC_ACCESSTOKEN"]
API_DOMAIN = os.environ["API_DOMAIN"]
api_url = f"https://{API_DOMAIN}"
headers = {"Authorization": f"Bearer {TOKEN}"}
response = requests.get(api_url, headers=headers)
print(response.text)
# {"message":"Hello, world!"}
参考資料
- Application Load Balancer を使用してユーザーを認証する
- ALB+Cognito環境でバックエンドに渡されるx-amzn-oidc-dataを検証する
- Lambda Web Adapterを使ってFastAPIのコンテナをLambdaにデプロイしてみた
- AWS Lambda Web AdapterとFastAPIで作る!Amazon BedrockのストリームAPIを使ったチャットアプリ
- aws-lambda-web-adapter/examples/fastapi/
- Amazon Cognito Hosted UIのCognitoドメインをカスタムドメインに変更してみた
- Amazon Cognito のカスタムスコープを使用して API Gateway の API へのアクセスを認可するにはどうすればよいですか?
- リソースサーバーを使用したスコープ、M2M、および API
- Amazon Cognito ユーザープールをオーソライザーとして使用して REST API へのアクセスを制御する
Discussion