GraphQLでオブジェクトタイプを作る際、DBのテーブル定義と一致させることがあると思います。
ただ、テーブルには閲覧範囲が異なるカラムが混在している事があるため、閲覧可否の判定が煩雑になることがあります。
例えばユーザー情報を保持するオブジェクトタイプについて考えてみます。
users テーブル
ユーザー情報が格納されているテーブル
ニックネームは全体に公開されますが、その他の情報は本人のみ閲覧可能な情報としています。
Name | Type | Nullable | Comment |
---|---|---|---|
id | bigint | false | |
name | varchar(255) | false | 名前。本人のみ公開 |
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!
}
このようにすることで、オブジェクトタイプが多少冗長にはなりますが、閲覧可否の判定はクエリー単位で考えれば良くなり実装をシンプルにすることができます。