#はじめに
最近、GraphQL-Rubyを使って認可を実装する機会があったのでまとめてみます。
用語的には認証と認可がありますが、今回は認可を扱います。(GraphQLは一般的には認証を扱ってません)
resolverやmutationに認可をつける
まずは、resolver
やmutation
に対しての認可に利用できるメソッドについてまとめます。
https://graphql-ruby.org/mutations/mutation_authorization.html
ready?
ready?
メソッドは、以下のようにresolverやmutationで使用できます。ready?
はmutation実行前に自動的に実行されます。戻り値がfalseの場合はnullが返されますが、例のようにGraphQL Errorを発生させた方がベターでしょう。
class Mutations::PromoteEmployee < Mutations::BaseMutation
def ready?(**args) # argsはmutationの引数
if !context[:current_user].admin?
raise GraphQL::ExecutionError, "Only admins can run this mutation"
else
true
end
end
def resolver(**args)
# mutation実行
end
end
authorized?
mutationにはauthorized?
メソッドも用意されています。実はこのメソッドはready?
メソッドとほとんど同じ動きをします。上のready?
をauthorized?
に書き換えても同様に動作します。唯一の違いはargumentsをロードする
かどうかです。
argumentsをロードするとは、以下のようにargumentを定義した場合にargumentのemployee_idからloadsで定義されているEmployeeオブジェクトを自動的にDBから取得してくれることを指します(多分)。
argument :employee_id, ID, required: true, loads: Types::Employee
上記の場合だと、引数で指定したidのemployeeオブジェクトが取得されます。故にauthorized?メソッドを利用すると、以下のようにも書くことができます。
argument :employee_id, ID, required: true, loads: Types::Employee
def authorized?(employee:)
context[:current_user].manager_of?(employee)
end
オブジェクトタイプに認可をつける
GraphQL-Rubyでは、関連を持つオブジェクト同士は、クライアント側で自由に取得できます。すると、本来は権限が必要なリソースにもアクセスできてしまうことがあります。それを防ぐためにオブジェクトタイプごとに認可を設定する方法をまとめます。
visible?を使う
https://graphql-ruby.org/authorization/visibility.html
visible?メソッドはその名の通り、スキーマ自体がユーザから見えなくなります。例えば、実験的な機能を特定のユーザのみに開放するみたいなユースケースが考えられます。
以下の例では、visible?メソッドの戻り値がfalseなら、graphqlエラーが返ることになります
class secretType < BaseObject
def self.visible?(context)
context[:current_user].admin?
end
field :hoge, Hoge, null: true
end
authorized?を使う
https://graphql-ruby.org/authorization/authorization.html
1番、認可っぽいのはこのauthorized?メソッドでしょうか。名前からしてそうですね。
以下の例では、current_userがobject.userと等しい場合は、Objectが返され、等しくない場合は、nullが返ります。
class secretType < BaseObject
def self.authorized?(object, context)
context[:current_user] === object.user
end
field :hoge, Hoge, null: true
end
また、authorized?メソッドがfalseの場合にエラーを返したい場合は、schemaのトップレベルにエラーを定義することができます。
class MySchema < GraphQL::Schema
def self.unauthorized_object(error)
raise GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions"
end
end
scopeを使う
https://graphql-ruby.org/authorization/scoping.html
authorized?メソッドは、そのオブジェクトを返すか、返さないかの二択でしたが、ユーザによって返すオブジェクトを制限したい場合もあると思います。その場合はscope
メソッドが便利です。
以下の場合は、adminユーザならば、オブジェクトを全て返し、それ以外はpublishedなproductを返します。
class prodcutType < BaseObject
def self.scope_items(items, context)
context[:current_user].admin? items : items.published
end
end
このscopeを適用するかどうかは、以下のようにfieldの引数で定義できます。
field :products, [Types::Product] scope: true
戻り値が配列の場合は、デフォルトでtrueになっており、値の場合はfalseになっています。
#まとめ
以上、GraphQL-Rubyの認可についてまとめました。Graphqlの認可は結構ややこしいので、しっかり整理していきたいです!