前回、Schemaで @aws_auth
や @ aws_cognito_user_pools
などを使って認可処理を書いてみました。
AWS AppSyncのSchemaで、認証・認可系ディレクティブの @aws_auth や @aws_cognito_user_pools などを試してみた - メモ的な思考的な
ただ、複数認証時に @aws_auth
が使えなかったため、Schemaだけでは複数認証時にAWS Cognitoのグループによる認可処理ができなさそうでした。
他の方法を探してみたところ、Schemaに紐づくリゾルバ内で $context
の値を参照することで、AWS CognitoのグループやHTTPリクエストヘッダを使った認可処理ができそうでしたので、メモを残します。
目次
- 環境
- リゾルバの $context について
- AWS Cognitoのグループを使った認可制御
- HTTPリクエストヘッダを使った認可制御
- schema.graphql でのmulti-auth directivesは、今のところ未対応 (PR #1524)
- ソースコード
- 環境構築ログ
環境
リゾルバの $context について
リゾルバとは、Schema (QueryやMutation、Subscription) とデータソースを紐付ける関数のようなものです。
リゾルバ内で参照できる変数 $context
とは
$context 変数は、リゾルバー呼び出しのすべてのコンテキスト情報が保持されるマップです。この変数の構造は次のとおりです。 { "arguments" : { ... }, "source" : { ... }, "result" : { ... }, "identity" : { ... }, "request" : { ... } }
です。
リゾルバーのマッピングテンプレートのコンテキストリファレンス - AWS AppSync
最初に、 $context
にはどんな値が設定されているかを確認してみます。
スキーマとして、以下を用意します。また、データソースは None
type Context { source: String identity: String claims: String sourceIp: String request: String headers: String } type Query { getContext: Context }
次にデータソースを指定します。今回は $context
の値を表示するだけなので、データソースには None
を指定します。
None データソースのリゾルバーマッピングテンプレートリファレンス - AWS AppSync
最後にリゾルバを作成します。リクエストテンプレートでは、中身を見たい項目を指定しておきます。
{ "version": "2017-02-28", "payload": { "source": $util.toJson($context.source), "identity": $util.toJson($context.identity), "claims": $context.identity.claims, "sourceIp": $util.toJson($context.identity.sourceIp), "request": $util.toJson($context.request), "headers": $util.toJson($context.request.headers) } }
レスポンステンプレートでは、リクエストテンプレートの中身をそのまま表示します。
$utils.toJson($context.result)
あとはAmplify frameworkを使って
Amplify.configure(awsconfig); // サインイン Auth.signIn(username, '11111111') // AppSync APIを呼ぶ await API.graphql( { query: queries.getContext, authMode: 'AMAZON_COGNITO_USER_POOLS' } ).then((event) => { console.log('ok'); console.log(event.data.getContext); }).catch((e) => { console.log('error!'); console.log(e); });
として、レスポンスをログ出力してみます。
結果は以下のような感じでした。
# claims: "{sub=xxx, cognito:groups=[member], <= groupが設定されている場合のみ存在するキー event_id=xxx, token_use=access, scope=aws.cognito.signin.user.admin, auth_time=nnn, iss=https://cognito-idp.<region>.amazonaws.com/<region>_xxx, exp=xxx, iat=xxx, jti=xxx, client_id=xxx, username=foo}" # headers: "{x-forwarded-for=xxx.xxx.xxx.xxx, yyy.yyy.yyy.yyy, cloudfront-is-tablet-viewer=false, cloudfront-viewer-country=JP, pragma=no-cache, via=2.0 xxx.cloudfront.net (CloudFront), cloudfront-forwarded-proto=https, origin=http://localhost:8080, content-length=149, cache-control=no-cache, host=<host>.appsync-api.<region>.amazonaws.com, x-forwarded-proto=https, accept-language=ja,en-US;q=0.9,en;q=0.8, user-agent=Mozilla/5.0 ..., cloudfront-is-mobile-viewer=false, accept=application/json, text/plain, */*, cloudfront-is-smarttv-viewer=false, accept-encoding=gzip, deflate, br, referer=http://localhost:8080/, content-type=application/json; charset=UTF-8, x-amz-cf-id=xxx, x-amzn-trace-id=Root=xxx, authorization=xxx, cloudfront-is-desktop-viewer=true, x-forwarded-port=443}" # identity: "{sub=xxx, issuer=https://cognito-idp.<region>.amazonaws.com/<region>_xxx, username=foo, claims={...}, sourceIp=[...], defaultAuthStrategy=ALLOW, groups=null}" # request: 実質headersしか入っていなかったので略
これより、
などを使った認可処理が書けそうでした。
AWS Cognitoのグループを使った認可制御
AWS Cognitoで認証されたユーザーかつユーザーがグループに所属している場合、 $context.identity.claims
にキー cognito:groups
が追加されます。
これを使い、リクエストテンプレートの先頭にて、VTL(Apache Velocity Template Language)を使って実装します。VTLの書き方はAppSyncのドキュメントの他、Javaまわりでも確認できます。
リゾルバーのマッピングテンプレートプログラミングガイド - AWS AppSync
なお、認可できない時は、 ユーティリティヘルパーの $utils.unauthorized()
を使ってエラーを返します。
リゾルバーのマッピングテンプレートのユーティリティリファレンス - AWS AppSync
全体はこんな感じです。
## Cognitoのユーザープールのユーザーのうち、Groupが "admin" の場合のみ、データを取得可能にする #set( $groups = $context.identity.claims.get("cognito:groups") ) ## ユーザーにグループが設定されていない場合は、キー自体が存在しないことに注意 #if( $util.isNull($groups) ) $util.unauthorized() #end #foreach( $group in $groups ) #if( $group != "admin" ) $util.unauthorized() #end #end ## 以降は、前述通りなので略 { "version": "2017-02-28", ... }
参考:AppSync + Cognitoによる認可制御 | Takumon Blog
HTTPリクエストヘッダを使った認可制御
こちらも同様に、リクエストテンプレートに記載します。
HTTPリクエストヘッダを見るとCloudFrontのヘッダーが追加されていたため*1、今回はそれを用いるようにしました。
参考:https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-device
## また、モバイルの時は、データ取得不可 ## CloudFrontのヘッダが取得できているので、それを使用(ふつうのUserAgentも取得できる) #if( $context.request.headers.get("cloudfront-is-mobile-viewer") == "true") $util.unauthorized() #end
schema.graphql でのmulti-auth directivesは、今のところ未対応 (PR #1524)
前回の記事の通り、AWS Amplify Consoleでは @aws_cognito_user_pools
などが使えたため、Amplifyでも使えるのかと思いましたが、現在は対応中のようです。
[WIP] Add support for new multi-auth AppSync directives by attilah · Pull Request #1524 · aws-amplify/amplify-cli
そのため、今のところは、AppSync Consoleにて手書きで @aws_cognito_user_pools
などを付けるしかなさそうです。
ソースコード
Githubに上げました。 resolver_auth
ディレクトリの中が今回のファイルです。
https://github.com/thinkAmi-sandbox/AWS_AppSync_Amplify-sample
環境構築ログ
今回試した時の環境構築ログです。
初期化
$ amplify init Note: It is recommended to run this command from the root of your app directory ? Enter a name for the project resolver_auth ? Enter a name for the environment dev ? Choose your default editor: Visual Studio Code ? Choose the type of app that you're building javascript Please tell us about your project ? What javascript framework are you using none ? Source Directory Path: src ? Distribution Directory Path: dist ? Build Command: npm run-script build ? Start Command: npm run-script start Using default provider awscloudformation For more information on AWS Profiles, see: https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html ? Do you want to use an AWS profile? Yes ? Please choose the profile you want to use default
Authを追加
Cognitoは新規作成します。
$ amplify add auth Using service: Cognito, provided by: awscloudformation The current configured provider is Amazon Cognito. Do you want to use the default authentication and security configuration? Manual configuration Select the authentication/authorization services that you want to use: User Sign-Up & Sign-In only (Best used with a cloud API only) Please provide a friendly name for your resource that will be used to label this category in the project: ResolverAuthResource Please provide a name for your user pool: ResolverAuthUserPool Warning: you will not be able to edit these selections. How do you want users to be able to sign in? Username Multifactor authentication (MFA) user login options: OFF Email based user registration/forgot password: Enabled (Requires per-user email entry at registration) Please specify an email verification subject: Your verification code Please specify an email verification message: Your verification code is {####} Do you want to override the default password policy for this User Pool? Yes Enter the minimum password length for this User Pool: 8 Select the password character requirements for your userpool: (Press <space> to select, <a> to toggle all, <i> to invert selection) Warning: you will not be able to edit these selections. What attributes are required for signing up? (Press <space> to select, <a> to toggle all, <i> to invert selection)Email Specify the app's refresh token expiration period (in days): 30 Do you want to specify the user attributes this app can read and write? No # 何も選択しない Do you want to enable any of the following capabilities? (Press <space> to select, <a> to toggle all, <i> to invert selection) Do you want to use an OAuth flow? No ? Do you want to configure Lambda Triggers for Cognito? No
pushして、Cognitoを生成します。
$ amplify push Current Environment: dev | Category | Resource name | Operation | Provider plugin | | -------- | -------------------- | --------- | ----------------- | | Auth | ResolverAuthResource | Create | awscloudformation | ? Are you sure you want to continue? Yes
Cognito作成後、ユーザーとグループを作成します。
APIを追加
$ amplify add api ? Please select from one of the below mentioned services GraphQL ? Provide API name: ResolverAuthAPI ? Choose an authorization type for the API Amazon Cognito User Pool ? Do you have an annotated GraphQL schema? No ? Do you want a guided schema creation? Yes ? What best describes your project: (Use arrow keys) ❯ Single object with fields (e.g., “Todo” with ID, name, description) One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”) ShinanoDolce:resolver_auth kamijoshinya$ amplify add api ? Please select from one of the below mentioned services GraphQL ? Provide API name: ResolverAuthAPI ? Choose an authorization type for the API Amazon Cognito User Pool ? Do you have an annotated GraphQL schema? No ? Do you want a guided schema creation? No ? Provide a custom type name MyType
ただ、自動生成されたスキーマだと @model
ディレクティブが付与されているため、DynamoDBを作ってしまいます。
そのため、resolver_auth/amplify/backend/api/ResolverAuthAPI/schema.graphql
を修正し、データソース None
に対応できるようにします。
type Context { source: String identity: String claims: String sourceIp: String request: String headers: String } type Query { getContext: Context }
また、APIで必要な
なども作成しておきます。
一通り終わったら、pushしてAPIを作成します。
その際、 ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
としておくと、schema.graphql
の内容に基づいて、 resolver_auth/src/graphql/queries.js
が生成されます。
今回はQueryだけ用意しましたので、 queries.js
のみが生成されます。
$ amplify push Current Environment: dev | Category | Resource name | Operation | Provider plugin | | -------- | -------------------- | --------- | ----------------- | | Api | ResolverAuthAPI | Create | awscloudformation | | Auth | ResolverAuthResource | No Change | awscloudformation | ? Are you sure you want to continue? Yes GraphQL schema compiled successfully. Edit your schema at path/to/resolver_auth/amplify/backend/api/ResolverAuthAPI/schema.graphql or place .graphql files in a directory at path/to/resolver_auth/amplify/backend/api/ResolverAuthAPI/schema ? Do you want to generate code for your newly created GraphQL API Yes ? Choose the code generation language target javascript ? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
アプリの作成と起動
# 作成して実装 $ touch package.json index.html webpack.config.js src/app.js # 必要なものをインストール $ npm install # amplifyもインストール $ npm install --save aws-amplify # 起動して動作確認 $ npm start
*1:AppSyncの前段にCloudFrontがいるのかなと思いましたが、それらしき資料は見つけられず...