概要
AWS AppSyncはAWS上にGraphQLサーバーを構築することができるマネージドサービスです。
APIを作る場合、「リソースの作成・編集・削除は、そのリソースを作成したユーザーしか許可しない」というような認可の仕組みが必要になります。
今回はAWS AppSyncをGraphQLサーバーとして使う場合の認可の実装方法をまとめてみたいと思います。
なお、AWS AppSyncのデータソースにはAWSのNoSQLデータベースであるAmazon DynamoDBを使います。
DynamoDBのスキーマ設計だけで実現できるパターン
Amazon DynamoDBはNoSQLデータベースで、テーブルのデータはあらかじめ設定したハッシュキーと任意で設定できるソートキーを使ったシンプルなクエリしかできません。
そのDynamoDBの設計によってアクセスできるリソースを制限することができます。
例として、UserとPostというモデルを持つアプリケーションを考えます。
UserはPostを複数作成することができます。
Userモデルが持つ情報
| 属性 | 役割 |
|---|---|
| id | ユーザーごとに一意なID |
| name | ユーザー名 |
Postモデルが持つ情報
| 属性 | 役割 |
|---|---|
| id | 投稿ごとに一意なID |
| content | 投稿の本文 |
| userId | 投稿したユーザーのID |
これをAmazon DynamoDBに格納するとき、以下のような2つのテーブルを設計したとします。
UserTable
| 属性 | 型 | テーブル |
|---|---|---|
| id | String | Hash Key |
| name | String |
PostTable
| 属性 | 型 | テーブル |
|---|---|---|
| id | String | Hash Key |
| content | String | |
| userId | String |
これに対して、「あるユーザーの投稿を取得したい」というアクセスパターンを実現するには、以下のようなGSIを作成します。
PostTable
| 属性 | 型 | テーブル | userId-index |
|---|---|---|---|
| id | String | Hash Key | |
| content | String | ||
| userId | String | Hash Key |
これで、PostTableに対して、userId = ?という検索が行えるようになりました。
AWS AppSyncでDynamoDBをデータソースにしたリゾルバーを作るとき、以下のようにリクエストマッピングテンプレートで指定することでインデックスに対してクエリを行うことができます。
{
"version": "2017-02-28",
"operation": "Query",
"index": "userId-index", ## インデックス名を指定
"query": {
"expression": "userId = :userId",
"expressionValues": {
":userId" : $util.dynamodb.toDynamoDBJson($ctx.identity.sub), ## セッション中のユーザーIDを取得
},
},
}
リゾルバーで使える$ctx変数には、セッション中のユーザーの情報が含まれています。
AWS AppSyncの認証モードをCognito User Poolsにしている場合は、$ctx.identity.subでCognitoユーザープールでのユーザーIDを取得することができます。
今回の例では、Postの保存時にuserId属性にこの$ctx.identity.subをリゾルバーでセットしている前提になります。
こうすることで、シンプルにユーザーのリソースに対するアクセス制限を実現できました。
一人のユーザーに結びつくようなモデルを扱う場合、テーブルのハッシュキーをuserIdにすると扱いやすくなることが多い印象です。
レスポンスマッピングテンプレートで認可を制御するパターン
「データソースから取ってきたデータによって、それをセッション中のユーザーが見られるかどうかが決まる」というケースは、このレスポンスマッピングテンプレートで制御するパターンが適用できるかもしれません。
先ほどと同じモデルを想定して、以下の例を考えます。
Postは、基本的には作成したユーザーしか閲覧できない。しかし、Postにpublic: trueという属性がセットされている場合は、作成者以外のユーザーでも閲覧することができる。
この場合、セッション中のユーザーが要求したPostにアクセスできるかどうかは、実際にDynamoDBからデータを取ってきてから決まります。
リクエストマッピングテンプレートはデータソースにデータを取得するための命令を書くので、まだデータソースからデータは得られていません。
そこで、レスポンスマッピングテンプレートを使います。
PostをIDで指定して1件取得するgetPostというGraphQLクエリのリゾルバーは、以下のように実装することができます。
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
},
}
## 該当するアイテムが見つからなかった場合
# if(!$ctx.result)
null
# end
## セッション中のユーザーのものではない and アイテムのpublic属性がセットされていなかった場合
# if($ctx.result.userId != $ctx.identity.sub && !$ctx.result.public)
$util.unauthorized()
# end
$util.toJson($ctx.result)
まとめ
他のデータソースにデータを取得して、それを使って認可を行うにはパイプラインリゾルバーが使えます。
パイプラインリゾルバーを使った認可の方法はまた別の記事でまとめたいと思います。