2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GraphQLAdvent Calendar 2021

Day 8

GraphQLオブジェクトタイプのフィールドは閲覧範囲を揃えて閲覧可否の判定を楽にしよう

Last updated at Posted at 2021-12-07

GraphQLでオブジェクトタイプを作る際、DBのテーブル定義と一致させることがあると思います。
ただ、テーブルには閲覧範囲が異なるカラムが混在している事があるため、閲覧可否の判定が煩雑になることがあります。

例えばユーザー情報を保持するオブジェクトタイプについて考えてみます。

users テーブル

ユーザー情報が格納されているテーブル
ニックネームは全体に公開されますが、その他の情報は本人のみ閲覧可能な情報としています。

Name Type Nullable Comment
id bigint false
name varchar(255) false 名前。本人のみ公開
email varchar(255) false メールアドレス。本人のみ公開
nickname varchar(255) false ニックネーム。全体に公開

実装するクエリー

usersテーブルに格納されているデータを取得する2種類のクエリーを考えます。

  • ログインユーザーが自身のid, name, email, nicknameを取得するクエリー
  • 任意のユーザーが指定したユーザーのid, nicknameを取得するクエリー

Userタイプ

まずはGraphQLのオブジェクトタイプを実装します。
どちらのクエリーでも使えるようにusersテーブルのスキーマと同じフィールドを定義しました。

type User {
  id: Int!
  name: String!
  email: String!
  nickname: String!
}

クエリー

次にクエリーを実装します。

viewer

こちらはログインユーザーの情報を返却するクエリーです。

type Query {
  viewer: User!
}

クライアントからは下記のクエリーを使います。

query Viewer {
  viewer {
    id
    email
    name
    nickname
  }
}

profile

こちらは指定したユーザーの情報を返却するクエリーです。

type Query {
  profile(userId: Int!): User!
}

クライアントからは下記のクエリーを使います。

query Profile($userId: Int!) {
  profile(userId: $userId) {
    id
    nickname
  }
}

ここまでの問題点

ここまでで要件を満たす2つのクエリーを作ることができました。
ただ、1点重大な問題点があります。

GraphQLは取得するフィールドをクライアントから指定できるのが特徴です。
ということはprofileクエリーを下記のように実行することもできます。

query Profile($userId: Int!) {
  profile(userId: $userId) {
    id
    email
    name
    nickname
  }
}

profileクエリーは誰でも実行できる仕様なので、fieldにnameやemailを加えて実行すると誰でもメールアドレスや名前が取得できてしまいます。

どうするか?

バックエンドの実装を作り込めばfield単位でアクセス制御をすることが可能です。
例えば、emailやnameはログインユーザーと一致しているユーザーの場合のみ返却するような制御を実装すればOKです。

ただ、今までGraphQLを使ってきた私の感覚では、field単位で閲覧可否の判定をすると実装が煩雑になり、また実装漏れをしてしまう可能性も高いと感じています。

そこで私が実践しているのは、オブジェクトタイプには閲覧範囲が異なるフィールドを持たないです。
オブジェクトタイプ内のfieldの閲覧範囲が一定であれば、クエリーの呼び出し時に閲覧可否の判定をすれば良くなります。

今回の例の場合、本人のみ閲覧可能なfieldを含んだものと誰でも閲覧可能なfieldだけの2つのオブジェクトタイプを作り、viewerとprofileからそれぞれを使うようにします。

type CurrentUser {
  id: Int!
  name: String!
  email: String!
  nickname: String!
}

type User {
  id: Int!
  nickname: String!
}

type Query {
  viewer: CurrentUser!
  profile(userId: Int!): User!
}

このようにすることで、オブジェクトタイプが多少冗長にはなりますが、閲覧可否の判定はクエリー単位で考えれば良くなり実装をシンプルにすることができます。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?