Help us understand the problem. What is going on with this article?

AWS AppSyncでリソースへのアクセス制限を実現する

概要

AWS AppSyncはAWS上にGraphQLサーバーを構築することができるマネージドサービスです。
APIを作る場合、「リソースの作成・編集・削除は、そのリソースを作成したユーザーしか許可しない」というような認可の仕組みが必要になります。

今回はAWS AppSyncをGraphQLサーバーとして使う場合の認可の実装方法をまとめてみたいと思います。
なお、AWS AppSyncのデータソースにはAWSのNoSQLデータベースであるAmazon DynamoDBを使います。

DynamoDBのスキーマ設計だけで実現できるパターン

Amazon DynamoDBはNoSQLデータベースで、テーブルのデータはあらかじめ設定したハッシュキーと任意で設定できるソートキーを使ったシンプルなクエリしかできません。

そのDynamoDBの設計によってアクセスできるリソースを制限することができます。

例として、UserPostというモデルを持つアプリケーションを考えます。
UserPostを複数作成することができます。

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は、基本的には作成したユーザーしか閲覧できない。しかし、Postpublic: 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)

まとめ

他のデータソースにデータを取得して、それを使って認可を行うにはパイプラインリゾルバーが使えます。
パイプラインリゾルバーを使った認可の方法はまた別の記事でまとめたいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした