AWS AppSyncを使ってGraphQL APIを公開する場合、 Default authorization mode
が必須なため、何らかの方法での認証を行う必要があります。
セキュリティ - AWS AppSync
認証されていないユーザーでもAPIを使えるようにしたいと考えた場合、簡単なのはAPI_KEY認証です。
ただし、APIで共通のAPIキーを使い、またAPIキーの有効期限があるなどの問題があり、ドキュメントにも
API キーは、最大 365 日間有効に設定可能で、該当日からさらに最大 365 日、既存の有効期限を延長できます。API キーは、パブリック API の公開が安全であるユースケース、または開発目的での使用が推奨されます。
と書かれています。
他の認証方法のうち、OPENID_CONNECT認証とAMAZON_COGNITO_USER_POOLS認証については、何らかの形でログインが必要になります。
残るはAWS_IAM認証ですが、
- 認証されていないユーザーがアクセスする場合、Amazon CognitoのIDプールの
認証されていないロール
が適用される - Amazon Cognitoには、
認証されていない ID に対してアクセスを有効にする
という設定がある
ことから、これを使えばよいのではと考えました。
そこで、AWS_IAM認証を使って、認証されていないユーザーに対してQueryを許可してみた時のメモを残します。
なお、記載に誤りがありましたら、ご指摘ください。
ちなみに、現在のAmplify CLI (1.8.2)でAppSync APIを作成する場合、AWS_IAM認証の設定を行えません。
Add AWS_IAM via Identity Pool as an authorization option when adding a GraphQL API · Issue #1386 · aws-amplify/amplify-cli
そのため、Amplify CLIで可能な限り作業しつつ、足りないことはAWSの各Consoleで設定する形にします。
目次
- 環境
- Amplifyモジュールの追加
- schema.graphqlの修正
- stacks/Todo.jsonの修正
- リクエスト/レスポンスマッピングテンプレートの作成
- pushして動作確認
- AWS_IAM認証化
- 動作確認
- ソースコード
環境
また、今回Queryを実行する対象のDynamoDBテーブル Todo
は以下とします。
項目 | 型 | 備考 |
---|---|---|
title | String | 必須 |
content | String |
Amplifyモジュールの追加
Amplify CLIを使って、必要なモジュールを追加していきます。
amplify init
$ amplify init Note: It is recommended to run this command from the root of your app directory ? Enter a name for the project unauthaccesswithIAM ? 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モジュールの追加
ポイントは、Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM)」を Yes
とすることです。これにより、IDプールの 認証されていない ID に対してアクセスを有効にする
がチェックされます。
$ amplify auth add 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, connected with AWS IAM controls (Enables per-user Storage features for images or oth er content, Analytics, and more) Please provide a friendly name for your resource that will be used to label this category in the project: UnauthDynamoDBResource Please enter a name for your identity pool. UnauthDynamoDBRIdentityPool # 認証されていないIDに対してアクセスを有効にするにチェックを入れる Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) Yes Do you want to enable 3rd party authentication providers in your identity pool? No Please provide a name for your user pool: UnauthDynamoDBRUserPool 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? No 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
APIモジュールの追加
今回は、Amplify CLIの実行後に、その内容を修正・追加する形をとります。
なお、前述の通り 「Choose an authorization type for the API」は
のどちらかしか選択できないため、ひとまず動作確認が楽な API key
を選んでおきます。
$ amplify api add ? Please select from one of the below mentioned services GraphQL ? Provide API name: UnauthDynamoDBAPI # API_KEY認証にする ? Choose an authorization type for the API API key ? Do you have an annotated GraphQL schema? No ? Do you want a guided schema creation? No ? Provide a custom type name Todo
なお、<root>/amplify/backend/api/UnauthDynamoDBAPI/build
ディレクトリにある
stacks/Todo.json
resolvers/
などは、amplify push
すると上書きされてしまうため、どこか別のところに保存しておくと参照できるので便利です。
schema.graphqlの修正
Amplify CLIで作成されたschema.graphqlでは、@model
ディレクティブがあるためにQuery/Mutation/Subscriptionができてしまいます。
そのため、今回必要なものだけに修正します。Queryの他に、きちんとアクセス制限されるかを確認するためにMutationも用意します。
type Todo { title: String! content: String } input CreateTodoInput { title: String! content: String } type Query { getTodo(title: String!): Todo } type Mutation { createTodo(input: CreateTodoInput!): Todo }
stacks/Todo.jsonの修正
こちらも今回不要なものがいくつもあるため、CloudFormationのドキュメントを参照しつつ、修正を加えます。
AmplifyにおけるCloudFormationの設定は、今のところJSONしか対応していないため、行数が多くなってしまうのが残念です。
Cloudformation template format. · Issue #1286 · aws-amplify/amplify-cli
# YAMLで書いた時のエラー Yaml is not yet supported. Please convert the CloudFormation stack ExistsDynamoDB.yaml to json.
Parametersまで
必要なパラメータだけに絞ります。
なお、AppSyncAPIのIDは、 AppSyncApiId
というパラメータ名で受け取れます。
How to resolve GetAttGraphQLAPIApiId in CustomResources.json · Issue #1109 · aws-amplify/amplify-cli
{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "Todo stack", "Metadata": {}, "Parameters": { "AppSyncApiId": { "Type": "String", "Description": "The id of the AppSync API associated with this project." }, "env": { "Type": "String", "Description": "The environment name. e.g. Dev, Test, or Production", "Default": "NONE" }, "DynamoDBBillingMode": { "Type": "String", "Description": "Configure @model types to create DynamoDB tables with PAY_PER_REQUEST or PROVISIONED billing modes.", "Default": "PAY_PER_REQUEST", "AllowedValues": [ "PAY_PER_REQUEST", "PROVISIONED" ] }, "S3DeploymentBucket": { "Type": "String", "Description": "The S3 bucket containing all deployment assets for the project." }, "S3DeploymentRootKey": { "Type": "String", "Description": "An S3 key relative to the S3DeploymentBucket that points to the root of the deployment directory." } }, "Resources": { ...
DynamoDBテーブルの作成
まずは、今回使用するDynamoDBテーブルの作成をResourcesに記述します。
今回は、必須設定の
- TableName
- KeySchema
- AttributeDefinitions
- BillingMode
のみ行い、後はデフォルトのままです。
"TodoTable": { "Type": "AWS::DynamoDB::Table", "Properties": { "TableName": { "Fn::Join": [ "-", [ "TodoUnauth", { "Ref": "env" } ] ] }, "KeySchema": [ { "AttributeName": "title", "KeyType": "HASH" } ], "AttributeDefinitions": [ { "AttributeName": "title", "AttributeType": "S" } ], "BillingMode": { "Fn::If": [ "ShouldUsePayPerRequestBilling", "PAY_PER_REQUEST", { "Ref": "AWS::NoValue" } ] } } },
また、 Conditions
に、 ShouldUsePayPerRequestBilling
の設定をします。
}, // Resources の閉じカッコ "Conditions": { "ShouldUsePayPerRequestBilling": { "Fn::Equals": [ { "Ref": "DynamoDBBillingMode" }, "PAY_PER_REQUEST" ] } } }
DynamoDBを操作するIAMロールを作成
AppSyncのDataSourceを指定する場合、 SerciceRoleArn
(データソースがリソースへの接続に使用するIAMロール) が必要になるため、IAMロールを作成します。
ポイントとしては、AppSyncでの利用で誤った操作をされないよう、PoliciesのActionは dynamodb:GetItem
だけ許可しておきます。
また、AppSyncで利用するためのAssumeRole設定を、AssumeRolePolicyDocumentに記載します。
IAMロール徹底理解 〜 AssumeRoleの正体 | DevelopersIO
"AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "appsync.amazonaws.com" }, "Action": "sts:AssumeRole" } ] },
全体は以下です。
"TodoIAMRole": { "Type": "AWS::IAM::Role", "Properties": { "RoleName": { "Fn::Join": [ "-", [ "TodoTableUnauthAccessRole", { "Ref": "env" } ] ] }, "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "appsync.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Policies": [ { "PolicyName": "DynamoDBAccess", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:GetItem" ], "Resource": [ { "Fn::Sub": [ "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}", { "tablename": { "Ref": "TodoTable" } } ] }, { "Fn::Sub": [ "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}/*", { "tablename": { "Ref": "TodoTable" } } ] } ] } ] } } ] } },
AppSyncのDataSourceを指定
ここまでで、DynamoDBテーブル & 操作するIAMロールができましたので、次はAppSyncまわりの設定です。
まずはDataSourceを設定します。
AWS::AppSync::DataSource - AWS CloudFormation
ポイントとしては ServiceRoleArn
に、先ほど作成した TodoIAMRole
の Arn
を指定することです。
"ServiceRoleArn": { "Fn::GetAtt": ["TodoIAMRole", "Arn"] },
全体です。
"TodoDataSource": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": {"Ref": "AppSyncApiId"}, "Name": "TodoTable", "Type": "AMAZON_DYNAMODB", "ServiceRoleArn": { "Fn::GetAtt": ["TodoIAMRole", "Arn"] }, "DynamoDBConfig": { "AwsRegion": { "Ref": "AWS::Region" }, "TableName": {"Ref": "TodoTable"} } } },
リゾルバーの設定
必要なのはQueryリゾルバーだけですが、今回AppSync Consoleからも確認できるようにMutaitionリゾルバーも用意します。
AWS::AppSync::Resolver - AWS CloudFormation
ポイントは
- DataSourceNameに、上記で作成したDataSourceのNameを指定
"Fn::GetAtt": ["TodoDataSource", "Name"]
- FieldNameは、GraphQLのQuery/Mutationの名前と合わせる
あたりです。
"GetTodoResolver": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": {"Ref": "AppSyncApiId"}, "DataSourceName": { "Fn::GetAtt": [ "TodoDataSource", "Name" ] }, "FieldName": "getTodo", "TypeName": "Query", "RequestMappingTemplateS3Location": { "Fn::Sub": [ "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", { "S3DeploymentBucket": { "Ref": "S3DeploymentBucket" }, "S3DeploymentRootKey": { "Ref": "S3DeploymentRootKey" }, "ResolverFileName": { "Fn::Join": [ ".", [ "Query", "getTodo", "req", "vtl" ] ] } } ] }, "ResponseMappingTemplateS3Location": { "Fn::Sub": [ "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", { "S3DeploymentBucket": { "Ref": "S3DeploymentBucket" }, "S3DeploymentRootKey": { "Ref": "S3DeploymentRootKey" }, "ResolverFileName": { "Fn::Join": [ ".", [ "Query", "getTodo", "res", "vtl" ] ] } } ] } } }, "CreateTodoResolver": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": {"Ref": "AppSyncApiId"}, "DataSourceName": { "Fn::GetAtt": [ "TodoDataSource", "Name" ] }, "FieldName": "createTodo", "TypeName": "Mutation", "RequestMappingTemplateS3Location": { "Fn::Sub": [ "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", { "S3DeploymentBucket": { "Ref": "S3DeploymentBucket" }, "S3DeploymentRootKey": { "Ref": "S3DeploymentRootKey" }, "ResolverFileName": { "Fn::Join": [ ".", [ "Mutation", "createTodo", "req", "vtl" ] ] } } ] }, "ResponseMappingTemplateS3Location": { "Fn::Sub": [ "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}", { "S3DeploymentBucket": { "Ref": "S3DeploymentBucket" }, "S3DeploymentRootKey": { "Ref": "S3DeploymentRootKey" }, "ResolverFileName": { "Fn::Join": [ ".", [ "Mutation", "createTodo", "res", "vtl" ] ] } } ] } } }
IDプールの「認証されていないロール」にアタッチするポリシーを作成
ここまででDynamoDBとAppSyncの設定が終わりました。
最後に、IDプールの 認証されていないロール
にアタッチする管理ポリシーを作成します。このポリシーは、後で「認証されていないロール」に手動でアタッチします。Amplify CLIがAWS_IAM認証に対応していないためです。
- 参考
ポイントとしては、Statementで
- Actionの
appsync:GraphQL
を許可 - ResourceはAppSyncのQueryのみ許可
を行うことです。
"UnauthAccessPolicy": { "Type": "AWS::IAM::ManagedPolicy", "Properties": { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ { "Fn::Sub": [ "arn:aws:appsync:${AWS::Region}:${AWS::AccountId}:apis/${apiID}/types/Query/*", { "apiID": { "Ref": "AppSyncApiId" } } ] } ] } ] }, "ManagedPolicyName": { "Fn::Sub": [ "MyUnauthAccessPolicy-${apiID}", { "apiID": { "Ref": "AppSyncApiId" } } ] } } },
リクエスト/レスポンスマッピングテンプレートの作成
リゾルバで指定した、リクエスト/レスポンスマッピングテンプレートを <root>/amplify/backend/api/UnauthDynamoDBAPI/resolvers
ディレクトリに作成します。
Query.getTodo.req.vtl
operationをTodoIAMRoleで指定した GetItem
にして、リクエストテンプレートを作成します。
{ "version": "2017-02-28", "operation": "GetItem", "key": { "title": $util.dynamodb.toDynamoDBJson($ctx.args.title) } }
Query.getTodo.res.vtl
結果をそのまま返します。
$util.toJson($context.result)
Mutation用のマッピングテンプレート
今回の動作確認用のMutation用マッピングテンプレートも用意します。
Mutation.createTodo.req.vtl
## [Start] Prepare DynamoDB PutItem Request. ** $util.qr($context.args.input.put("createdAt", $util.defaultIfNull($ctx.args.input.createdAt, $util.time.nowISO8601()))) $util.qr($context.args.input.put("updatedAt", $util.defaultIfNull($ctx.args.input.updatedAt, $util.time.nowISO8601()))) $util.qr($context.args.input.put("__typename", "Todo")) { "version": "2017-02-28", "operation": "PutItem", "key": { "title": { "S" : "${context.args.input.title}" } }, "attributeValues": $util.dynamodb.toMapValuesJson($context.args.input), "condition": { "expression": "attribute_not_exists(#title)", "expressionNames": { "#title": "title" } } } ## [End] Prepare DynamoDB PutItem Request. **
Mutation.createTodo.res.vtl
$util.toJson($context.result)
pushして動作確認
ここまでで設定ができたため、
$ amplify push
します。
その後、AppSync ConsoleのQueriesで、MutationとQueryが成功することを確認します。
Mutation
mutation CreateTodo($params: CreateTodoInput!) { createTodo(input: $params) { title content } }
QUERY_VALUESです。
{ "params": { "title": "Hello, title!", "content": "Hello, content!" } }
実行すると以下が表示されます。
{ "data": { "createTodo": { "title": "Hello, title!", "content": "Hello, content!" } } }
Query
Mutationと同様です。
query GetTodo($title: String!) { getTodo(title: $title) { title content } }
QUERY_VALUESです。
{ "params": { "title": "Hello, title!", "content": "Hello, content!" }, "title": "Hello, title!" }
結果です。
{ "data": { "createTodo": { "title": "Hello, title!", "content": "Hello, content!" } } }
AWS_IAM認証化
ここまではAPY_KEY認証でしたので、AWS_IAM認証へと切り替えます。
AppSync Consoleでの認証切り替え
Settings にある Default authorization mode
を、 AWS Identity and Access Management (IAM)
へと変更し、 Save
をクリックします。
IAMロールで、認証されていないロールにポリシーをアタッチ
まずは、CognitoのIDプールにて、編集画面から 認証されていないロール
のロール名を確認します(今回の場合、 unauthaccess-dev-20190720202229-unauthRole
)。
次に、IAMのロールで確認したロール名を選択し、stacks/Todo.jsonの UnauthAccessPolicy
セクションで作成したポリシー (MyUnauthAccessPolicy-<AppSync API ID>
)をアタッチします。
動作確認
Amplify Framework JavaScriptを使って、ボタンを押したらQuery/Mutationを実行する動作確認アプリを作成します。
以下ではindex.htmlとsrc/app.jsのみ掲載します。
- package.json
- webpack.config.js
は、Amplify公式チュートリアルを流用したものを用意します。
Amplify JavaScript - Getting Started
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Amplify Framework</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <div class="app"> <button id="query">Run Query</button> </div> <div> <input type="text" id="title" placeholder="title"> <button id="mutation">Run Mutation</button> </div> <script src="main.bundle.js"></script> </body> </html>
src/app.js
import Amplify, {API} from 'aws-amplify'; import awsconfig from './aws-exports'; import * as queries from "./graphql/queries"; import * as mutations from "./graphql/mutations"; const queryButton = document.getElementById('query'); queryButton.addEventListener('click', () => { console.log('Run Query!'); Amplify.configure(awsconfig); API.graphql( { query: queries.getTodo, authMode: 'AWS_IAM', variables: { title: "Hello, title!" } } ).then((data) => { console.log(data); }) .catch((e) => { console.log('error!'); console.log(e) }); }); const mutationButton = document.getElementById('mutation'); mutationButton.addEventListener('click', () => { console.log('Run Mutation!'); const title = document.getElementById('title').value; Amplify.configure(awsconfig); API.graphql( { query: mutations.createTodo, authMode: 'AWS_IAM', variables: { input: { title: title, content: "hello" } } } ).then((data) => { console.log(data); }) .catch((e) => { console.log('error!'); console.log(e) }); });
結果
$ npm start
してアプリを起動後、動作を確認してみます。
すると、
- Queryは成功
- MutationはHTTP 401 エラー
となり、認証されていないユーザーに対してQueryのみ許可できました。
ソースコード
Githubに上げました。 unauth_access
ディレクトリ以下が今回のファイルです。
https://github.com/thinkAmi-sandbox/AWS_AppSync_Amplify-sample