AWS AppSyncのSchemaには、簡単にユーザー認証・認可を行える
などのディレクティブが用意されています。
そこで、実際に試してみた時のメモを残します。
目次
環境
また、認証・認可に使うAWS Cognitoのユーザーとグループは以下の通りです。
ユーザー名 | グループ |
---|---|
foo | admin |
bar | member |
また、今回使用するDynamoDBのレコードは
id | name | description |
---|---|---|
111 | cook | breakfast |
となっている前提です。
また、今回の検証を行うための環境構築方法については、長いので一番最後に記載しています。
長いのでまとめ
@aws_auth
は、Cognitoのグループで制御できる- ただし、認証方法が1つ(Default authorization modeしか設定されていない)場合に限る
- 複数認証では動作しないため、他の
@aws_api_key
などとは併用できない
- 複数認証では動作しないため、他の
- ただし、認証方法が1つ(Default authorization modeしか設定されていない)場合に限る
複数認証のディレクティブ
@aws_api_key
などは、queryやtype全体の他、フィールド単位でも制御できる- ただし、ディレクティブを付けてしまうと、Default authorization modeは無視される
- また、
@aws_cognito_user_pools
では、Cognitoのグループを使った制御ができない
以降、それぞれを検証した結果を書いていきます。
認証方法が1つの場合
@aws_authについて
@aws_auth
は、Amazon Cognitoを使って認証・認可する時に使います。
https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/security.html#amazon-cognito-user-pools-authorization
リゾルバレベルでの制限が可能
ドキュメントにもある通り、 @aws_auth
はリゾルバ (query, mutation, subscription) レベルで制限することが可能です。
例えば、
type Query { awsAuth(filter: ModelTodoFilterInput, limit: Int, nextToken: String): ModelTodoConnection @aws_auth(cognito_groups: ["admin"]) }
と、Query awsAuth
はCognitoのグループが admin
しか実行できないとします。
この場合、ユーザー bar
(Cognitoグループは member
) で awsAuth
を実行すると、
{data: {…}, errors: Array(1)} data: awsAuth: null __proto__: Object errors: Array(1) 0: data: null errorInfo: null errorType: "Unauthorized" locations: [{…}] message: "Not Authorized to access awsAuth on type Query" path: ["awsAuth"]
と、エラーになり、Cognitoのグループでの制限ができていることが分かります。
typeの制限はできない
一方、 @aws_auth
を使って、
# descriptionを admin グループしか取得できなくしたい type Todo { id: ID! name: String! description: String @aws_auth(cognito_groups: ["admin"]) } # nextTokenを admin グループしか取得できなくしたい type ModelTodoConnection { items: [Todo] nextToken: String @aws_auth(cognito_groups: ["admin"]) }
と、特定のフィールドだけを制限することや
type ModelTodoConnection @aws_auth(cognito_groups: ["admin"]) { items: [Todo] nextToken: String }
と、type全体を制限することはできないようです。
そのため、上記のtypeに対して値を取得すると
{id: "111", name: "cook", description: "breakfast"}
と、値が取得できてしまうので、注意が必要です。
認証方法が複数の場合
今年の5月に、AppSyncでは複数の認証方法がサポートされました。
- AWS AppSync、GraphQL API の複数の認証タイプ設定のサポートを開始
- Using multiple authorization types with AWS AppSync GraphQL APIs | AWS Mobile Blog
また、Schemaにディレクティブも追加されたようです。
・@aws_api_key —A field uses API_KEY for authorization. ・@aws_iam —A field uses AWS_IAM for authorization. ・@aws_oidc —A field uses OPENID_CONNECT for authorization. ・@aws_cognito_user_pools —A field uses AMAZON_COGNITO_USER_POOLS for authorization. https://aws.amazon.com/jp/blogs/mobile/using-multiple-authorization-types-with-aws-appsync-graphql-apis/
今回は aws_api_key
と aws_cognito_user_pools
を試してみます。
まず、AWS AppSync ConsoleのSettingsは以下とします。
また、Amplifyクライアントは、注釈がない限り、 Amazon Cognito User Pool
認証を行います。
await API.graphql({ query: queries.<name>, authMode: 'AMAZON_COGNITO_USER_POOLS' }) ...
複数認証環境下では、@aws_auth による制限ができなさそう
先ほどの例であげたQuery
type Query { awsAuth(filter: ModelTodoFilterInput, limit: Int, nextToken: String): ModelTodoConnection @aws_auth(cognito_groups: ["admin"]) }
はそのままに、複数認証環境へと変更し、ユーザー bar
でQuery awsAuth
を実行したところ
{id: "111", name: "cook", description: "breakfast"}
と、先ほどの結果とは異なっていました。
そのため、複数認証環境下では @aws_auth
による制限ができないようです。
@aws_api_key や @aws_cognito_user_pools での制限
続いて、 @aws_api_key
や @aws_cognito_user_pools
での制限を見ます。
Queryレベルでの制限
まずは、先ほどと同じように、Queryに対し、 @aws_cognito_user_pools
を追加します。
type Query { awsCognito(filter: ModelTodoFilterInput, limit: Int, nextToken: String): TodoConnectionCognitoAuth @aws_cognito_user_pools }
この状態でCognito認証を使ってアクセスすると、以下のようにフィールド description
が取得できます。
{id: "111", name: "cook", description: "breakfast"}
続いて、Amplifyでの認証方式をCognitoからAPI Keyへと変更して、実行します。
await API.graphql( { query: queries.awsCognito, authMode: 'API_KEY' // authMode: 'AMAZON_COGNITO_USER_POOLS' }
結果は
{data: {…}, errors: Array(1)} data: {awsCognito: null} errors: Array(1) 0: data: null errorInfo: null errorType: "Unauthorized" locations: [{…}] message: "Not Authorized to access awsCognito on type Query" path: ["awsCognito"]
と、認証エラーになりました。
これはQueryに @aws_cognito_user_pools
ディレクティブしかなかったことから、API Keyでの認証が拒否されたようです。
もし、API Keyでの認証をパスするためには
- Query - awsCognito
- Type - TodoConnectionCognitoAuth
- Type - TodoCognitoAuth
の3ヶ所で @auth_api_key
を付ける必要があります。
今回は、順を追って確認します。
まず、Queryにだけ @aws_api_key
を付けてみます。
type Query { awsCognito(filter: ModelTodoFilterInput, limit: Int, nextToken: String): TodoConnectionCognitoAuth @aws_cognito_user_pools @aws_api_key }
結果は
{data: {…}, errors: Array(2)} data: {awsCognito: {…}} errors: Array(2) 0: data: null errorInfo: null errorType: "Unauthorized" locations: [{…}] message: "Not Authorized to access items on type TodoConnectionCognitoAuth" path: (2) ["awsCognito", "items"] 1: data: null errorInfo: null errorType: "Unauthorized" locations: [{…}] message: "Not Authorized to access nextToken on type TodoConnectionCognitoAuth" path: (2) ["awsCognito", "nextToken"]
と、レスポンスの TodoConnectionCognitoAuth
が認証エラーとなりました。
続いて、 TodoConnectionCognitoAuthに対し、 @aws_api_key
を追加します。
type TodoConnectionCognitoAuth @aws_api_key { items: [TodoCognitoAuth] nextToken: String }
結果は
{data: {…}, errors: Array(3)} data: {awsCognito: {…}} errors: Array(3) 0: data: null ... message: "Not Authorized to access id on type TodoCognitoAuth" path: (4) ["awsCognito", "items", 0, "id"] 1: data: null ... message: "Not Authorized to access name on type TodoCognitoAuth" path: (4) ["awsCognito", "items", 0, "name"] 2: data: null ... message: "Not Authorized to access description on type TodoCognitoAuth" path: (4) ["awsCognito", "items", 0, "description"]
と、itemのtype TodoCognitoAuth
が認証エラーとなりました。
最後に、TodoCognitoAuthに @aws_api_key
を追加します。
type TodoCognitoAuth @aws_api_key { id: ID! name: String! description: String }
ようやく、すべての結果を取得できました。
{id: "111", name: "cook", description: "breakfast"}
ここではqueryだけを試しましたが、mutationやsubscriptionも同様です。
フィールドレベルでの制限
複数認証向けのディレクティブ @aws_api_key
や @aws_cognito_user_pools
などは、フィールドごとに制限を加えることができます。
例えば、以下はフィールド description
に対し、Cognito認証の時のみ取得できるようにしています。
type TodoWithoutDescription @aws_api_key { id: ID! name: String! description: String @aws_cognito_user_pools }
API Key認証でQueryを実行してみると
{data: {…}, errors: Array(1)} data: {awsWithoutDescription: {…}} errors: Array(1) 0: data: null errorInfo: null errorType: "Unauthorized" locations: [{…}] message: "Not Authorized to access description on type TodoWithoutDescription" path: (4) ["awsWithoutDescription", "items", 0, "description"]
と、API Key認証では取得できない項目 description
をqueryで指定したため、エラーになりました。
もし、API Key認証でエラーにならないようにするには、Amplifyから実行するQuery定義
awsWithoutDescription(filter: $filter, limit: $limit, nextToken: $nextToken) { items { id name description } nextToken } }
からフィールド description
を削除します。
awsWithoutDescription(filter: $filter, limit: $limit, nextToken: $nextToken) { items { id name } nextToken } }
すると、
{id: "111", name: "cook"}
と、権限のあるフィールドデータが取得できました。
複数認証時のDefault authorization modeとディレクティブについて
前述のフィールドレベルでの制限では、
- Default authorization modeはCognito認証
- クライアントはAPI Key認証
で試しました。
Default authorization modeの動きをもう少し確認するため、Schemaはそのままに
- Default authorization modeは、そのままCognito認証
- クライアントは、Cognito認証へと切り替え
として実行します。
すると、
{data: {…}, errors: Array(2)} data: {awsWithoutDescription: {…}} errors: Array(2) 0: ... message: "Not Authorized to access items on type TodoConnectionWithoutDescription" path: (2) ["awsWithoutDescription", "items"] 1: ... message: "Not Authorized to access nextToken on type TodoConnectionWithoutDescription" path: (2) ["awsWithoutDescription", "nextToken"]
とエラーになりました。
Schemaを確認すると
type TodoConnectionWithoutDescription @aws_api_key { items: [TodoWithoutDescription] nextToken: String }
と、 @aws_api_key
はあるものの、Default authorization modeのディレクティブがありません。
これより、
- ディレクティブを何も付けない場合、Default authorization modeでの認証となる
- Default authorization mode以外のディレクティブを付けた場合、Default authorization modeでの認証は不可
となることが分かりました。
これに対応するには
type TodoConnectionWithoutDescription @aws_api_key @aws_cognito_user_pools { items: [TodoWithoutDescription] nextToken: String } type TodoWithoutDescription @aws_api_key @aws_cognito_user_pools { id: ID! name: String! description: String @aws_cognito_user_pools }
と、Default authorization modeのディレクティブ (今回は @aws_cognito_user_pools
)を付ける必要があります。
まとめ(再掲)
@aws_auth
は、Cognitoのグループで制御できる- ただし、認証方法が1つ(Default authorization modeしか設定されていない)場合に限る
- 複数認証では動作しないため、他の
@aws_api_key
などとは併用できない
- 複数認証では動作しないため、他の
- ただし、認証方法が1つ(Default authorization modeしか設定されていない)場合に限る
複数認証のディレクティブ
@aws_api_key
などは、queryやtype全体の他、フィールド単位でも制御できる- ただし、ディレクティブを付けてしまうと、Default authorization modeは無視される
- また、
@aws_cognito_user_pools
では、Cognitoのグループを使った制御ができない
ソースコード
GitHubに上げました。ディレクトリ directive_auth
の中が今回のファイルです。
https://github.com/thinkAmi-sandbox/AWS_AppSync_Amplify-sample
環境構築
今回の環境は以下の流れで作成しましたので、こちらもメモとして残しておきます。
なお、Amplify経由で作成できたAWSリソースの詳細も記録していますが、Amplifyのバージョンが上がると変わるかもしれません。
Amplify のGetting started のStep1.とStep2.を実施
以下の流れで行いました。
https://aws-amplify.github.io/docs/js/start
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 directive_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
AWS Amplify Authentication module によるAWS 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 # IAMでログイン可能にする 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 other content, Analytics, and more) # 適当な名前をつける # アンダースコアがあると:The resource name must be at least 1 character and no more than 128 and only contain alphanumeric characters. Please provide a friendly name for your resource that will be used to label this category in the project: directiveauthresource # 適当なID Pool名をつける Please enter a name for your identity pool. directiveauthidpool # 認証なしのログインは認めない Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) No # 3rdパーティの認証は許可しない Do you want to enable 3rd party authentication providers in your identity pool? No # 適当なユーザープール名を作る Please provide a name for your user pool: directiveauthuserpool # 変更できない項目:ログインはユーザ名で行う Warning: you will not be able to edit these selections. How do you want users to be able to sign in when using your Cognito User Pool? Username # 2段階認証はoff Multifactor authentication (MFA) user login options: OFF # Eメールをベースに登録などを行う Email based user registration/forgot password: Enabled (Requires per-user email entry at registration) # Eメールの内容 Please specify an email verification subject: Your verification code Please specify an email verification message: Your verification code is {####} # ユーザープールのパスワードポリシーを更新:8文字で数字だけ (テストが手間なので) 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: Requires Numbers # 変更できない項目:ユーザー作成の時に必要な項目はメールのみ Warning: you will not be able to edit these selections. What attributes are required for signing up? Email # トークンの期限は30日 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 # OAuthは不要 Do you want to use an OAuth flow? No Successfully added resource resourceofauthdirective locally
Authentication module の環境ができたら、AWSにpushしておきます。
$ amplify push
Cognitoの確認
ユーザープール
Cognitoにて、できあがったユーザープールを確認します。現時点では以下のような設定がされていました。
分類 | 項目 | 値 |
---|---|---|
属性 | エンドユーザーのサインイン | ユーザー名 |
必須の標準属性 | ||
カスタム属性 | 無し | |
ポリシー | パスワード強度 | 8文字の数字 |
自己サインアップ | ユーザーに自己サインアップを許可する | |
有効期限 | 7 (日) | |
MFAそして確認 | 多要素認証 | オフ |
どの属性を確認 | Eメール | |
新規ロール名 | 新規作成・設定済 | |
アドバンスドセキュリティ | 有効化するか | いいえ |
メッセージのカスタマイズ | SESリージョン | 作成したリージョン |
FROM Eメールアドレス | デフォルト | |
REPLY-TO Eメールアドレス | <空欄> | |
Amazon SES 設定を通じて E メールを送信 | いいえ - Cognito を使用 (デフォルト) | |
検証メッセージ - 検証タイプ | コード | |
検証メッセージ - Eメールの件名 | Your verification code | |
検証メッセージ - Eメールのメッセージ | Your verification code is {####} | |
招待メッセージ - SMSメッセージ | ユーザー名は {username}、仮パスワードは {####} です。 | |
招待メッセージ - Eメールの件名 | 仮パスワード | |
招待メッセージ - Eメールのメッセージ | ユーザー名は {username}、仮パスワードは {####} です。 | |
タグ | <空欄> | |
デバイス | ユーザーのデバイスを記憶するか | いいえ |
アプリクライアント | アクセス権限のあるクライアント | 2つ用意される(xxx_app_clientWeb/xxx_app_client)、clientWebにはアプリクライアントのシークレットが無い |
トリガー | <空欄> | |
分析 | <空欄> | |
アプリクライアントの設定 | IDプロバイダとOAuth2.0設定 | 2つのクライアントとも設定なし(空欄) |
ドメイン名 | ドメインの使用 | <空欄> |
UIのカスタマイズ | エンドユーザーエクスペリエンス | エラーが出てる(There has to be an existing domain associated with this user pool) |
リソースサーバー | <空欄> | |
IDプロバイダー | <設定なし> | |
属性マッピング | <設定なし> |
なお、AmplifyではMFAを無効化できませんでした。pushした際にロールが作られてしまっているためです。
ただし、開発者がユーザーを作る場合にはメールなどの検証をOKにできるため、開発時点ではこのままで問題なさそうです。
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/how-to-create-user-accounts.html
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/signing-up-users-in-your-app.html#managing-users-accounts-email-testing
あるいは、開発時にCognitoへ登録するユーザについて、メールアドレスを以下にすれば良さそうです。
Eメールを配信するアクションをテストする場合、次のいずれかの E メールアドレスを使用してハードバウンスを回避します。 - テスト用に所有し、使用している E メールアカウントのメールアドレス。自分の E メールアドレスを使用すると、Amazon Cognito からの E メールが送信されます。この E メールでは、検証コードを使用して、アプリのサインアップエクスペリエンスをテストできます。ユーザープールの E メールメッセージをカスタマイズした場合は、正しく表示されていることを確認します。 - メールボックスシミュレーターのアドレス (success@simulator.amazonses.com)。シミュレーターのアドレスを使用した場合、メールは Amazon Cognito から正しく送信されますが、表示することはできません。このオプションは、検証コードを使用する必要がなく、E メールのメッセージを確認する必要がない場合に役立ちます。 - 任意のラベルを追加したメールボックスシミュレーターのアドレス (例: success+user1@simulator.amazonses.com または success+user2@simulator.amazonses.com)。E メールは、Amazon Cognito よりこれらのアドレスに正しく送信されますが、送信されたメールを表示することはできません。このオプションは、複数のテストユーザーをユーザープールに追加してサインアッププロセスをテストし、各テストユーザーに一意の E メールアドレスがある場合に便利です。 https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/signing-up-users-in-your-app.html#managing-users-accounts-email-testing
IDプール
続いて、IDプールの設定を確認します。
分類 | 項目 | 値 |
---|---|---|
IDプール名 | auth_directive_identity_pool__dev | |
認証されていないロール | authdirective-dev-xxx-unauthRole | |
認証されたロール | authdirective-dev-xxx-authRole | |
認証されていないID | 認証されていない ID に対してアクセスを有効にする | チェックなし |
認証プロバイダー | Cognito - ユーザープールID | 上記のユーザープールID |
Cognito - アプリクライアントID | 2つ用意される(xxx_app_clientWeb/xxx_app_client) | |
Cognito - 認証されたロールの選択 | デフォルトのロールを使用 | |
同期をプッシュします | 未設定 | |
Cognitoストリーム | 未設定 | |
Cognitoイベント | Sync Trigger | アクションなし |
Cognitoでユーザーとグループを作る
今回は、管理者ユーザー(foo)と、一般ユーザー(bar)を用意します。
項目 | 値 |
---|---|
ユーザー名 | foo |
この新規ユーザーに招待を送信しますか | チェックを外す |
仮パスワード | 22222222 |
電話番号 | 空欄 |
電話番号を検証済にしますか | チェックを外す |
Eメール | foo@example.com |
Eメールを検証済にしますか | チェックする |
fooとbarは、ユーザー名とメールアドレスが異なるだけで、あとは同じです。
なお、アカウントのステータスが FORCE_CHANGE_PASSWORD
となっているので、初めて使う時にパスワードを変更する必要がある点に注意が必要です。
続いて、グループ (adminとmember)の2つを作成します。
項目 | 値 |
---|---|
名前 | admin、もしくはmember |
説明 | <空欄> |
IAMロール | <選択せず> |
優先順位 | <空欄> |
その後、各ユーザーにグループを割り当てればOKです。
AWS AppSync API作る
今回はamplifyを使って作成します。
$ amplify add api # GraphQLのAPIを作成 ? Please select from one of the below mentioned services GraphQL # API名は適当に ? Provide API name: DirectiveAuthAPI # 初期データ投入のため、ひとまずAPI Key認証としておく ? Choose an authorization type for the API API key # GraphQLのスキーマ注釈は不要 ? Do you have an annotated GraphQL schema? No # スキーマ作成のガイドを頼む ? Do you want a guided schema creation? Yes # 一番簡単なスキーマを作る ? What best describes your project: Single object with fields (e.g., “Todo” with ID, name, description) # スキーマはできたそのものを利用する ? Do you want to edit the schema now? No
これでAPIのひな形ができたため、クラウドにpushします。
$ amplify push Current Environment: dev | Category | Resource name | Operation | Provider plugin | | -------- | ----------------------- | --------- | ----------------- | | Api | AuthDirectiveAPI | Create | awscloudformation | | Auth | resourceofauthdirective | No Change | awscloudformation | # pushを続けて問題ない ? Are you sure you want to continue? Yes GraphQL schema compiled successfully. Edit your schema at path/to/auth_directive/amplify/backend/api/AuthDirectiveAPI/schema.graphql or place .graphql files in a directory at path/to/auth_directive/amplify/backend/api/AuthDirectiveAPI/schema # GraphQL APIのコードを生成してもらう ? Do you want to generate code for your newly created GraphQL API Yes # JavaScript製 ? Choose the code generation language target javascript # 保管先はデフォルトのままで ? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js # GraphQLのオペレーションは自動生成してもらう ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes # ネストはデフォルトのまま ? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
できあがったAPI
以下の通りです。
- Schema: query + mutation + subscriptionあり
- Data source : TodoTable というDynamoDBができた
- Functions, Queries : 空
分類 | 項目 | 値 |
---|---|---|
Settings | Primary auth mode | API KEY |
Default authentication mode | API key |
できあがったDynamoDB
こちらも以下の通りです。
分類 | 項目 | 値 |
---|---|---|
ストリームの詳細 | ストリーム有効 | はい |
表示タイプ | 新旧イメージ | |
テーブルの詳細 | テーブル名 | Todo-xxx |
プライマリパーティションキー | id(文字列) | |
プライマリソートキー | <空欄> | |
ポイントインタイムリカバリ | 無効 | |
暗号化タイプ | KMS | |
有効期限(TTL)属性 | 無効 | |
テーブルの状態 | 有効 | |
読み込み/書き込みキャパシティーモード | オンデマンド | |
プロビジョンド読み込みキャパシティーユニット | - | |
プロビジョンド書き込みキャパシティーユニット | - |
DynamoDBにデータ投入
AppSync ConsoleのQueriesに以下の設定を行います。
mutation createTodo($createinput: CreateTodoInput!) { createTodo(input: $createinput) { id name description } }
また、QUERY_VARIABLESにも追加します。
{ "createinput": { "id": "1", "name": "cook", "description": "breakfast" } }
実行結果は以下となり、DynamoDBにデータが登録されました。
{ "createinput": { "id": "1", "name": "cook", "description": "breakfast" } }
Amplifyアプリの作成
Getting start に従い、ローカルにAmplifyアプリを作成します。
https://aws-amplify.github.io/docs/js/start?platform=purejs
- package.jsonつくる
- npm install
- webpack.config.jsつくる
- index.htmlつくる
- src/app.jsつくる
最後にコンパイルと起動をして完了です。
$ npm start