AWS AppSyncのSchemaで、認証・認可系ディレクティブの @aws_auth や @aws_cognito_user_pools などを試してみた

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 などとは併用できない
  • 複数認証のディレクティブ @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では複数の認証方法がサポートされました。

 
また、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_keyaws_cognito_user_pools を試してみます。

まず、AWS AppSync ConsoleのSettingsは以下とします。

  • Default authorization mode
  • Additional authorization providers

 

また、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 などとは併用できない
  • 複数認証のディレクティブ @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にて、できあがったユーザープールを確認します。現時点では以下のような設定がされていました。

分類 項目
属性 エンドユーザーのサインイン ユーザー名
必須の標準属性 email
カスタム属性 無し
ポリシー パスワード強度 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にできるため、開発時点ではこのままで問題なさそうです。

 
あるいは、開発時に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