API Gateway (HTTP API) のJWTオーソライザー と Cognitoユーザープールを使って OAuth 2.0 の "Client Credentials Grant" を実装する

記事タイトルとURLをコピーする

こんにちは
技術課の山本です

ゴールデンウィークは群馬にある至仏山に登ってきました
降雪後の朝焼け前に登り始めたので人の足跡も無い静かな世界でした

「仕事を頑張ってまた山に行くぞ」
と気持ちを新たにしました

API Gateway (HTTP API) のJWTオーソライザー と Cognitoユーザープールを使って OAuth 2.0 の "Client Credentials Grant" を実装する

OAuth 2.0 の "Client Credentials Grant"

OAuth 2.0 の "Client Credentials Grant" については
以下の RFC に詳しく解説があります

datatracker.ietf.org

図(抜粋)

OAuth2.0で認証するクライアントが自身の管理下にあるサーバーなどであり
シークレットの管理を厳重に行える場合に利用可能な認証手段です
サーバー上に保管したシークレットを用いて認証サーバーからアクセストークンを取得し
アクセストークンを使用してアプリケーションに接続します

AWS のサービス別資料では19,20スライド目に出てきます

動画:
youtu.be

API Gateway (HTTP API) のJWTオーソライザー と Cognitoユーザープールを使って "Client Credentials Grant" を実装する

構成図はこんな感じになります

クライアント目線で見るとやることは以下になります

  1. CognitoユーザープールのアプリクライアントIDと 生成したクライアントシークレットをサーバーに保管
  2. アプリクライアントIDと クライアントシークレットを認証ヘッダに入れてCognitoユーザープールのトークンエンドポイントにアクセストークンをリクエスト
  3. 発行されたアクセストークン(有効期限 1時間)を使って API Gateway にアクセス
  4. アクセストークンが使用可能なものである場合は API Gateway と統合している Lambda 等にアクセスする (誤ったトークンの場合はアクセス拒否となる)
  5. API Gateway と統合している Lambda 等は アクセストークン内のクレームをもとにリクエストを処理

環境構築

Cognito ユーザープールの作成

名前をつけます

アプリクライアントを追加します(これより前の設定はユーザー認証に関する設定項目のため変更していません)

アプリクライアントに名前を付けます
[クライアントシークレットを生成]にチェックが入っていることを確認します
クライアントシークレットを使って認証するため他のチェックは外します(一番下は外せない)

他は変更せずに プールの作成をします

作成したユーザープールにリソースサーバーを追加します

名前、識別子、スコープ、説明を追加します

アプリクライアントの設定をします
[許可されている OAuth フロー]の[Client credentials]にチェックします
[許可されているカスタムスコープ]はリソースサーバーに設定したものを選びます

トークンエンドポイントにドメイン名を設定します
( 独自ドメインを使用することもできます )
任意の名前を入力し右側にある[使用可能かチェック]のボタンを押します
上に[このドメインは使用できます]と出るので右下の[変更の保存]を押します
ドメイン名が設定できました

API Gateway (HTTP API) に統合する Lambda の作成

簡易的に受け取ったヘッダの内容を全て出力する Lambda を作成しておきます(Node.js 14.x)

exports.handler = async (event) => {
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify(event),
    };
    return response;
};

API Gateway (HTTP API) の作成

HTTP API を作成します

作成した Lambda を選択します
APIに任意の名前を付けます

/test_jwt へのリクエストが Lambda にルーティングされるようにします

ステージはデフォルトのまま 作成します

デプロイしたデフォルトのステージを見にいくとURLの記載があるのでメモします

URL のパス: /test_jwt に curl や WEBブラウザを使い アクセスすると 受信ヘッダを出力する Lambda の実行結果が表示されます
また ステータスコードは 200 が返ってくることを確認します

API Gateway (HTTP API) に JWT オーソライザーを追加

先に Cognito のユーザープールID と アプリクライアント ID をメモしておきます
ユーザープールID は Cognitoの以下の画面にあります

アプリクライアント ID はCognitoの以下の画面にあります

API Gateway に JWT オーソライザーを追加していきます

オーソライザーのタイプ にJWTを選択します
任意の名前を付けます
[発行者 URL]は以下になります

https://cognito-idp.<Cognitoの利用リージョン名>.amazonaws.com/<CognitoのユーザープールID>

[対象者]は Cognitoユーザープールのアプリクライアント IDになります

JWTオーソライザーを設定したので もう一度 パス: /test_jwt に curl や WEBブラウザを使い アクセスしましょう
すると今度はアクセストークンが無いため認証エラーになります(ステータスコードは401)

構築した "Client Credentials Grant" の環境を試す

1. CognitoユーザープールのアプリクライアントIDと 生成したクライアントシークレットをサーバーに保管

Cognitoユーザープールの以下画面から取得します

トークンエンドポイントのURLも控えておきます

2. アプリクライアントIDと クライアントシークレットを認証ヘッダに入れてCognitoユーザープールのトークンエンドポイントにアクセストークンをリクエスト

以下のように curl コマンドを実行します
補足:Authorizationヘッダには "アプリクライアントID:クライアントシークレット" をbase64 エンコードした文字列を入れて Basic 認証をします

# 変数定義
APP_CLIENT_ID=<アプリクライアントID>
CLIENT_SECRET=<クライアントシークレット>
TOKEN_END_POINT=<トークンエンドポイント>
CODE=`echo -n ${APP_CLIENT_ID}:${CLIENT_SECRET} | base64`

# トークン取得
curl -X POST ${TOKEN_END_POINT}/oauth2/token \
-H "Authorization: Basic $CODE" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=${APP_CLIENT_ID}"

access_tokenを取得できました

有効期限は 3600秒(1時間)になっています(変更不可)

参考:トークンエンドポイント - Amazon Cognito

3. 発行されたアクセストークン(有効期限 1時間)を使って API Gateway にアクセス

以下のように curl コマンドを実行します

# 変数定義
API_GATEWAY_URL=<API Gateway のURL>
API_GATEWAY_PATH=/test_jwt #APIのパス
ACCESS_TOKEN=<アクセストークン>

# API Gateway にアクセス
curl ${API_GATEWAY_URL}${API_GATEWAY_PATH} -H "Authorization:$ACCESS_TOKEN"

Lambda が実行されました
ステータスコード 200 が返ってきました
受信ヘッダには アクセストークン内のクレームも入っていました
クレームの中にはCognitoユーザープールのリソースサーバーに設定したスコープが含まれています

補足

API Gateway のAPIに認可スコープを設定する

API Gateway のAPIに認可スコープを設定することもできます
その場合は「リソースサーバーの識別子/スコープ名」という記載の仕方になります

Cognito ユーザープールの以下画面で確認できます

設定した認可スコープを持っていないとAPIにアクセス出来ないという制限をかけることになります

API Gateway の JWT オーソライザーに アプリクライアント を追加する

API Gateway の JWT オーソライザーには複数のアプリクライアント を追加できます

つまり 1つのCognitoユーザープールに複数のアプリクライアント を作成し
それぞれに異なるスコープを設定し API Gateway に認証させることも可能になっています

例:
最初にCognitoユーザープールのリソースサーバーに 書き込み用のスコープを追加します

次にCognitoユーザープールにアプリクライアントを追加します (上の「環境構築」と同じ手順のため詳細な手順割愛)

新しく作ったアプリクライアントの「許可されているカスタムスコープ 」には書き込み用のスコープを指定します

新しく作ったアプリクライアントIDを確認し API Gateway の JWT オーソライザーに追加します

これで新しく作ったアプリクライアントでも API Gateway にJWT認証させることができるようになりました
試しに追加したアプリクライアントのアプリクライアントIDとシークレットを使ってアクセストークンを発行し API Gateway にアクセスしてみたところ 認証が通りました
そしてスコープが 書き込み用のスコープになっていました

おしまい

参考にしたドキュメント

ユーザープールのリソースサーバーを定義する - Amazon Cognito

ユーザープールのアプリクライアントの設定 - Amazon Cognito

トークンエンドポイント - Amazon Cognito

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

※ トークンの検証をCognitoが行うように見えるため図を少し修正しました (2022/5/26)

山本 哲也 (記事一覧)

カスタマーサクセス部のエンジニア。2024 Japan AWS Top Engineers に選んでもらいました。

今年の目標は Advanced Networking – Specialty と Machine Learning - Specialty を取得することです。

山を走るのが趣味です。今年の目標は 100 km と 100 mile を完走することです。 100 km は Gran Trail みなかみで完走しました。OSJ koumi 100 で 100 mile 砕け散りました。どこかで 100 mile やりたいです。

基本的にのんびりした性格です。座右の銘は「いつか着く」