tseki blog

my study room

AWS Cognito の client credential grant を JWT オーソライザーで試してみた

REST API に無い HTTP API の機能の一つに JWT オーソライザーがあります。

JWT オーソライザーを使用した HTTP API へのアクセスの制御 - Amazon API Gateway

AWS Cognito UserPool 以外の ID プロバイダーを使っているときに、
場合によっては Lambda オーソライザーを作り込まなくても対応が可能となっています。

今回は、
AWS Cognito で client credential grant を使う場合の HTTP API の認可処理を
JWT オーソライザーを使って行ってみたいと思います。

概要

全体の構成は次のとおりです。

f:id:aba_0921:20210306185944p:plain
構成図

諸々のリソースの作成

  • Cognito ユーザープール
    • リソースサーバー
    • アプリクライアント
  • Lambda 関数
  • HTTP API

基本的には既定の設定のままで大丈夫です。

ユーザープールのドメイン設定を行ってトークンエンドポイントへのアクセスを可能にしておきます。

また、アプリクライアントの設定で Client credentials の OAuth フローを有効にし、
作成したリソースサーバーのスコープにチェックを入れます。

f:id:aba_0921:20210306191932p:plain
アプリクライアントの設定

JWT オーソライザーの作成

HTTP API に JWT オーソライザーを作成してルートにアタッチします。

f:id:aba_0921:20210306193019p:plain
オーソライザーの設定

JWT オーソライザーでは、こちらのドキュメントにあるような検証を行ってくれるそうです。

JWT オーソライザーを使用した HTTP API へのアクセスの制御 - Amazon API Gateway

設定する項目は次のとおりです。

設定項目 説明
IDソース Authorization ヘッダーを指定します。
発行者 JWTに含まれる issuer を指定します。
発行者の jwks_uri から取得した公開鍵でトークンを検証します。
この場合のユーザープールの jwks_uri
https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_jD9OiE4y8/.well-known/jwks.json
になります。
対象者 Cognito ユーザープールが発行したアクセストークンを検証するために
アプリクライアントの client_id を入力します。
認可スコープ 設定しない場合は特にチェックが行われませんが、
今回はスコープを指定してみました。
「このルートへアクセスするには、いずれかのスコープが必要」
という設定が可能です。

テスト

Postman で動作を確認します。

1. アクセストークンの発行

トークンエンドポイントにアクセスしてアクセストークンを発行します。

f:id:aba_0921:20210306200043p:plain
Basic 認証の設定

f:id:aba_0921:20210306200122p:plain
リクエストBodyの設定

次のようなレスポンスが得られ、
アクセストークンが取得できました。

f:id:aba_0921:20210306200213p:plain
レスポンス

2. HTTP API へのアクセス (アクセストークン無し)

まず、アクセストークンが無い状態でアクセスします。

f:id:aba_0921:20210306200743p:plain
アクセスが拒否される

アクセストークンが無い状態ではアクセスが拒否されました。

3. HTTP API へのアクセス (アクセストークンあり)

次に、アクセストークンを付与してアクセスします。

f:id:aba_0921:20210306201909p:plain
アクセストークンを設定

f:id:aba_0921:20210306201954p:plain
アクセスが成功する

アクセスが許可され、Lambdaからレスポンスが返ることが確認できました。

4. Lambda の event に含まれる情報

Lambda の event を確認します。

{
    "version": "2.0",
    "routeKey": "ANY /",
    "rawPath": "/",
    "rawQueryString": "",
    "headers": {
        "accept": "*/*",
        "accept-encoding": "gzip, deflate, br",
        "authorization": "Bearer eyJraWQiOiI0cXV3dnRpTG5TTVlaRzhTeEVcL0xQdHlkZUxiR1FoQ3ZkNUw5cXVcL2tkMDg9IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiI1YnVtdmVnYnRzbmtnODFlYWtlN2ozZGJvYSIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiY29tLmV4YW1wbGUudGVzdFwvdGVzdCIsImF1dGhfdGltZSI6MTYxNTAyODUwNywiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLmFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb21cL2FwLW5vcnRoZWFzdC0xX2pEOU9pRTR5OCIsImV4cCI6MTYxNTAzMjEwNywiaWF0IjoxNjE1MDI4NTA3LCJ2ZXJzaW9uIjoyLCJqdGkiOiJiMzRjNjgyOC02NjUwLTQ5ZGMtYmU1YS01ZmExYjc4NTZiYjgiLCJjbGllbnRfaWQiOiI1YnVtdmVnYnRzbmtnODFlYWtlN2ozZGJvYSJ9.WZmp1r1VnGBbwfbH5_w9vtq41Fu4yqNQW-kPAUZOyrmBCR1ZRvrBpH11GVyNBUYSyx1uzTo7paPnfwmrO2B1YM0qg3H0Vv9rMtg4v_7ZnFQWdf1Kry8iGOPiHyBry2JeB0xeU7wx8Phg1amW_1SibgQ39Ta_8syD0gXbCU2UaAZi6xgWjFitHlB6jebJ7efWNi2YdzEAdpXZoH7Ct6K68mTD5lWDg0R-47ixyCTsLNlejVFxbyOGpFOVUlYiKlA8m_EOd9-D1UGihoRntgI9d87t9-1_K2tttQ7HyXOxsXucyHvwd0XqNETtEfUknxMF7s33J2A3INAJ8-d-9HRm8Q",
        "content-length": "0",
        "host": "0o5uiabhb4.execute-api.ap-northeast-1.amazonaws.com",
        "postman-token": "85d05ba0-36c4-4cb6-b197-f0252075af52",
        "user-agent": "PostmanRuntime/7.26.10",
        "x-amzn-trace-id": "Root=1-6043654a-64df179b5529cad61b9e1819",
        "x-forwarded-for": "<ipアドレス>",
        "x-forwarded-port": "443",
        "x-forwarded-proto": "https"
    },
    "requestContext": {
        "accountId": "<アカウントid>",
        "apiId": "0o5uiabhb4",
        "authorizer": {
            "jwt": {
                "claims": {
                    "auth_time": "1615028507",
                    "client_id": "5bumvegbtsnkg81eake7j3dboa",
                    "exp": "1615032107",
                    "iat": "1615028507",
                    "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_jD9OiE4y8",
                    "jti": "b34c6828-6650-49dc-be5a-5fa1b7856bb8",
                    "scope": "com.example.test/test",
                    "sub": "5bumvegbtsnkg81eake7j3dboa",
                    "token_use": "access",
                    "version": "2"
                },
                "scopes": [
                    "com.example.test/test"
                ]
            }
        },
        "domainName": "0o5uiabhb4.execute-api.ap-northeast-1.amazonaws.com",
        "domainPrefix": "0o5uiabhb4",
        "http": {
            "method": "GET",
            "path": "/",
            "protocol": "HTTP/1.1",
            "sourceIp": "106.72.178.224",
            "userAgent": "PostmanRuntime/7.26.10"
        },
        "requestId": "bwzDmjpMNjMEJag=",
        "routeKey": "ANY /",
        "stage": "$default",
        "time": "06/Mar/2021:11:19:38 +0000",
        "timeEpoch": 1615029578123
    },
    "isBase64Encoded": false
}

event.requestContext.authorizer.jwt にて claimsscopes が渡ってきていることが確認できます。

制限事項

現時点での次のような引き上げ不能なクォータが設定されています。
要件を満たすか事前の確認を。

リソースまたはオペレーション デフォルトのクォータ
オーソライザーあたりの対象者数 50
ルートあたりのスコープ数 10
JSON ウェブキーセットエンドポイントのタイムアウト 1500 ms
OpenID Connect 検出エンドポイントのタイムアウト 1500 ms

現時点では REST API の使用量プランのような
API Key を使ったスロットリングを行うことは出来ず、
ルートやアカウント単位でのスロットリング設定のみとなっているようです。

最後に

とても簡単な設定で認可処理の実装が出来ましたし、
HTTP API の使い勝手が非常に良いと感じました。

「早い、安い、シンプル」な
HTTP API を使える場面がこれからも増えるといいなぁと思います。