Rails GraphQL provides field-level authorization through the authorize method and the AuthorizedField module. Authorization checks run during request execution and can prevent field resolution based on custom logic.
Authorization is separate from authentication. It determines what authenticated users can access.
The authorization system is implemented in the AuthorizedField module:
# From lib/rails/graphql/field/authorized_field.rbmodule Field::AuthorizedField # Add settings for authorization or a block to execute def authorize(*args, **xargs, &block) @authorizer = [args, xargs, block] self end # Return the settings for the authorize process def authorizer @authorizer if authorizable? end # Checks if the field should go through authorization def authorizable? defined?(@authorizer) endend
class Ability include CanCan::Ability def initialize(user) user ||= User.new if user.admin? can :manage, :all else can :read, Post, published: true can :update, Post, user_id: user.id end endend
# From lib/rails/graphql/field/authorized_field.rbmodule Proxied def authorizer super || field.authorizer end def authorizable? super || field.authorizable? endend
If a proxied field doesn’t have its own authorization, it uses the original field’s authorization.
field(:sensitive).authorize do return false unless current_user.present? current_user.has_permission?(:view_sensitive)end
Clear Error Messages
Provide helpful error messages:
unless authorized? request.report_error( "You need #{required_role} role to access this", extensions: { code: "FORBIDDEN" } ) return falseend
Avoid N+1 Queries
Preload authorization data:
field(:posts).before_resolve do # Preload user abilities @abilities = current_user.abilities.index_by(&:subject_type)end.authorize do @abilities['Post']&.can_read?end
Use Policy Objects
Centralize authorization logic:
field(:post).authorize do |id:| PostPolicy.new(current_user, Post.find(id)).show?end
RSpec.describe "Post authorization" do let(:admin) { create(:user, :admin) } let(:user) { create(:user) } it "allows admins to view all posts" do result = execute(<<~GQL, user: admin) { posts { id } } GQL expect(result[:errors]).to be_nil end it "denies regular users" do result = execute(<<~GQL, user: user) { adminPosts { id } } GQL expect(result[:errors]).to be_present expect(result[:errors][0][:extensions][:code]).to eq("FORBIDDEN") endend