こんにちは、広野です。
Amazon Cognito 認証を使用してアプリから AWS AppSync API を呼び出すとき、デフォルトではリゾルバのみでは Cognito ユーザーの ID や Cognito グループぐらいしか属性を取得できません。
Cognito ユーザーの ID さえわかれば、AWS Lambda 関数で Amazon Cognito の API を呼び出せば他の属性を取得することはできます。ですが、AWS Lambda 関数を使用することでレスポンスが遅くなるのは嫌です。本記事では、AWS Lambda 関数抜きで実現する方法を紹介します。
背景
まず、AWS 公式ドキュメントを読むと。 ※ここでは、JavaScript リゾルバで説明します。VTL も文法は違えど、内容は同じです。
長々と書いてありますが、コンテキストと呼ばれる引数 (ctx) の中に、AWS AppSync がアプリから受け取った情報が格納されます。Amazon Cognito ユーザー関連の情報は ctx.identity の中にあります。
ログに落とすと以下のようなデータが格納されています。
{
"identity": {
"claims": {
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"device_key": "ap-northeast-1_f074a9ab-30c8-48d2-9ff8-3f265cc90368",
"cognito:groups": [
"BASIC"
],
"iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX",
"client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"origin_jti": "bca4c698-3df8-4d2d-a224-ac0095385a98",
"event_id": "48eeef11-6aa5-4e77-81f7-69c6e4faaeae",
"token_use": "access",
"scope": "aws.cognito.signin.user.admin",
"auth_time": 1735803851,
"exp": 1736061086,
"iat": 1736039486,
"jti": "317acaa7-fa7b-4ff5-ad32-f0a02b399664",
"username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"defaultAuthStrategy": "ALLOW",
"groups": [
"BASIC"
],
"issuer": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX",
"sourceIp": [
"xxx.xxx.xxx.xxx"
],
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}
一通り見て気付くことは、以下です。
- ユーザー ID、ユーザー名、グループ属性はある
- email 属性が無い
- カスタム属性が無い
例えばユーザーのメールアドレスを使用した処理をしたいとか、カスタム属性によって処理の実行を制御したいときに、このままでは AWS Lambda 関数が必要になってしまいます。AWS AppSync を使うからには、AWS Lambda 関数は使用したくありません。
こうなっている原因は、説明が後回しになりましたが赤線を引いた “token_use”: “access” という部分で表されています。
AWS AppSync はデフォルトでは JWT トークンの中のアクセストークンが使用され、アクセストークン内に含まれている情報が限られているためにこの状況が発生しています。
比較して、Amazon API Gateway で Cognito オーソライザーを使用するときは ID トークンを使用します。以前私が書いた記事を紹介しますが、リクエストの Authorization ヘッダーに ID トークンを格納して送る、と紹介していました。
AWS AppSync でも、ID トークンを使用した Cognito 認証ができれば、アクセストークンよりもユーザー情報が多く格納されるため、この問題を解決できるのでは?と思った次第です。
解決方法
解決方法は、アプリから AWS AppSync API にリクエストを送るときの Authorization ヘッダーに ID トークンを格納することになります。
ヒントとなるドキュメントは、AWS Amplify UI のドキュメントにあります。カスタムヘッダーを設定するコードのサンプルが紹介されています。
ただし、これだけでは具体的にどう書けば今回のユースケースが動くのかわかりませんでしたが、色々と調べた結果動く方法が見つかりました。React の例になりますが、以下のコードになります。
import { Amplify } from 'aws-amplify'; import { Authenticator, useAuthenticator } from '@aws-amplify/ui-react'; import { fetchAuthSession } from 'aws-amplify/auth'; //Cognito, AppSync 連携設定 Amplify.configure( { Auth: { Cognito: { userPoolId: import.meta.env.VITE_USERPOOLID, userPoolClientId: import.meta.env.VITE_USERPOOLWEBCLIENTID, identityPoolId: import.meta.env.VITE_IDPOOLID } }, // 通常は、以下の部分だけを書く。 API: { GraphQL: { endpoint: import.meta.env.VITE_APPSYNC_URL, region: import.meta.env.VITE_REGION, defaultAuthMode: 'userPool', } } }, // 追加で以下のカスタムヘッダーを上とは切り離して書く。(これが重要) { API: { GraphQL: { headers: async () => ({ Authorization: ( await fetchAuthSession() ).tokens?.idToken?.toString() }) } } } );
アプリ内に、使用する Amazon Cognito リソースや AWS AppSync リソースを設定するコードを書くのですが、そこにカスタムヘッダーを追加する設定を書きます。ただし、カスタムヘッダーに格納する値は ID トークンですので、動的に変わります。Amazon Cognito の設定が反映されている前提で、Amazon Cognito にユーザーのセッション情報を問い合わせる fetchAuthSession を実行した結果から ID トークンを抜き出し、格納するコードになっています。
正直内部的な動きはわかっていませんが、こう書くことで AWS AppSync API にリクエストするときのトークンを ID トークンに置き換えることができました。検証した結果、トークンの自動更新も機能していました。
結果
結果、AWS AppSync リゾルバ内で取得するコンテキスト情報がどう変わったかを紹介します。背景の章で紹介したオリジナルのコンテキストと比較するため ctx.identity の中身をお見せします。
{ "identity": { "claims": { "sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "cognito:groups": [ "BASIC" ], "email_verified": true, "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX", "cognito:username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "origin_jti": "5c010020-e36b-46c5-be5c-579c63b68373", "aud": "xxxxxxxxxxxxxxxxxxxxxxxxxx", "event_id": "24fb81ef-5e04-4d29-ac28-810ce689f7ac", "token_use": "id", "auth_time": 1736259678, "exp": 1736281278, "iat": 1736259678, "custom:zokusei": "test", "jti": "7e980548-297d-4514-bdd0-7862550f6def", "email": "hirono@xxxxx.xxx" }, "defaultAuthStrategy": "ALLOW", "groups": [ "BASIC" ], "issuer": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX", "sourceIp": [ "xxx.xxx.xxx.xxx" ], "sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } }
赤線を引いた箇所が変更点です。
“token_use”: “id” になっており、ID トークンが使われていることがわかります。
“custom:zokusei”: “test” が追加されました。これは私がテスト的に Amazon Cognito に追加したカスタム属性です。これでカスタム属性に応じた制御をかけることができますね。
“email”: “hirono@xxxxx.xxx” が追加されました。これで Cognito ユーザーのメールアドレスを使用した処理ができますね。
カスタム属性については、前提として Amazon Cognito のアプリケーションクライアントの「属性権限」設定で、読み取りを有効にしていないと ID トークン経由で連携されません。ご注意ください。
さらに補足です。
AWS AppSync が受け取るコンテキストには、リクエストヘッダー情報も含まれています。その中の Authorization ヘッダーの中にトークンが格納されているわけですので、そこから base64 でデコードして同じ情報を取得することも可能と思われます。ですが、結局 ctx.identity.claims に含まれている情報と同じなので、意味はないです。
まとめ
いかがでしたでしょうか。
AWS Lambda 関数を使いたくない一心で調査を頑張りました。本件の解決については、同僚のメンバーにかなり協力して頂きました。感謝!です。
本記事が皆様のお役に立てれば幸いです。