graphql-tools の makeExecutableSchema で Directive を定義して簡易認可を実装してみる
Schema directives | GraphQL Tools によると
graphql-tools の makeExecutableSchema
で Directive を実装できるようなので
簡易的な認可ロジックを実装してみます。
あくまで Directive のハンズオンなので認可ロジックはとても簡単なものです。
認証情報は context
経由で処理の部分に渡すことにします。
「タイトルは自由に取得できるけど本文は許された人にしか閲覧できないドキュメント」を取得するためのクエリを定義していきます。
import { graphql } from 'graphql' import { makeExecutableSchema } from 'graphql-tools' const typeDefs = ` directive @auth(permitted: [String!]) on FIELD type Document { title: String! content: String! @auth(permitted: ["asa-taka"]) } type Query { document: Document! } ` // Directive の処理を定義する const directiveResolvers = { // @auth Directive の処理を定義 auth(next, src, args, ctx, info) { return next().then(res => { // コンテクスの認証ユーザが `permitted` に含まれていたら値を素通し if (args.permitted.includes(ctx.auth.name)) return res // 認証エラーを返してあげる (*˘꒳˘*) やさしい // info の中身は複雑なので適当に調べながら... const path = info.path throw new Error(`User not permitted: ${path.prev.key}.${path.key}`) }) }, } const schema = makeExecutableSchema({ typeDefs, directiveResolvers }) const query = `{ document { title content } }` // データソースの準備 const rootValue = { document: { title: 'My Document', content: 'Awesome contents...' }, } // コンテクスト経由で認証情報を渡してやる const context = { auth: { name: 'asa-taka' } } graphql(schema, query, rootValue, context).then(console.log, console.error)
これを実行すると
{ data: { document: { title: 'My Document', content: 'Awesome contents...' } } }
が得られます。ここから認証情報を
const context = { auth: { name: 'some-other-user' } }
と変更すると
{ errors: [ { Error: User not permitted: document.content
とエラーが返されます。ここから更に認証フィールドである content
をクエリから除外して
const query = `{ document { title } }`
とすると
{ data: { document: { title: 'My Document' } } }
エラーなくデータが得られます。
ディレクティブの定義
基本的な使い方は Schema directives | GraphQL Tools に書いてある通りです。
まず GraphQLSchema
内で利用する Directive を定義します。
GraphQL Schema Language では以下のように Custom Directive を定義できます
directive @auth(permitted: [String!]) on FIELD
次に定義した Directive についての処理の実装を定義します。
graphql-tools の makeExecutableSchema
では directiveResolver
プロパティでディレクティブの処理を定義できます。
const directiveResolvers = { auth(next, src, args, ctx, info) { return next().then(res => { if (args.permitted.includes(ctx.auth.name)) return res const path = info.path throw new Error(`User not permitted: ${path.prev.key}.${path.key}`) }) }, } const schema = makeExecutableSchema({ typeDefs, directiveResolvers })
GraphQL の Directive も Apollo のおかげで意外と簡単に実装できますね。
これまで
...と、Directive 実装に苦労してきた経緯があるのですが、makeExecutableSchema
はどう実装されているのか気になりますね。