⚡️ Pro Feature ⚡️ This feature is bundled with GraphQL-Pro.
NOTE: A new Pundit integration%20and%20%5BCanCan%20integration%5D(/authorization/can_can_integration)%20are%20available.%20They%20leverage%20GraphQL-Ruby’s%20new%20%5Bbuilt-in%20auth%5D(/authorization/overview) system and has better support for inheritance and customization. If possible, use those instead!
GraphQL::Pro
provides a comprehensive, unified authorization framework for the GraphQL runtime.
Fields and types can be authorized at runtime, rejected during validation, or hidden entirely. Default authorization can be applied at schema-level
GraphQL::Pro
integrates out-of-the-box Pundit support and CanCan support and supports custom authorization strategies
To use authorization, specify an authorization strategy in your schema:
class Schema < GraphQL::Schema
# ...
authorization :pundit
# or:
# authorization :cancan
# authorization CustomAuthClass
end
(See below for details on these strategies.)
Then, provide a current_user:
in your execution context:
# Authenticate somehow:
current_user = User.find(session[:current_user_id])
# Then pass the user as `current_user:`
result = MySchema.execute(query_string, context: { current_user: current_user })
current_user
will be used by the authorization hooks as described below.
You can specify a fallback auth configuration for the entire schema:
class Schema < GraphQL::Schema
# Always require logged-in users to see anything:
authorization(..., fallback: { view: :logged_in })
end
This rule will be applied to fields which don’t have a rule of their own or a rule on their return type.
You can customize the current_user:
context key with authorization(..., current_user: ...)
:
class Schema < GraphQL::Schema
# Current user is identified as `ctx[:viewer]`
authorization :pundit, current_user: :viewer
end
The authorization will use the specified key to find the current user in ctx
.
When a resolve
function returns an object or list of objects, you can assert that the current user has permission to access that object. The authorize
keyword defines a runtime permission.
You can specify a permission at field-level, for example:
# Only allow access to this `balance` if current user is the owner:
field :balance, AccountBalanceType, authorize: :owner
# This is the same:
field :balance, AccountBalanceType do
authorize :owner
# ...
end
Also, you can specify authentication at type-level, for example:
class AccountBalanceType < GraphQL::Schema::Object
# Only billing administrators can see
# objects of this type:
authorize :billing_administrator
# ...
end
Field-level and type-level permissions are additive: both checks must pass for a user to access an object.
Type-level permissions are applied according to an object’s runtime type (unions and interfaces don’t have authorization checks).
If an object doesn’t pass permission checks, it is removed from the response. If the object is part of a list, it is removed from the list. You can override this behavior with the unauthorized_object
hook.
You can also limit access to fields based on their parent objects with parent_role:
. For example, to restrict a student’s GPA to that student:
class StudentType < GraphQL::Schema::Object
field :name, String, null: false
field :gpa, Float, null: true do
# only show `Student.gpa` if the
# student is the viewer:
authorize parent_role: :current_user
end
end
This way, you can serve a subset of fields based on the object being queried.
When an object fails a runtime authorization check, the default behavior is:
nil
instead; ORYou can override this behavior by providing a schema-level unauthorized_object
function:
class Schema < GraphQL::Schema
# Override this hook to handle cases when `authorized?` returns false for an object:
def self.unauthorized_object(error)
# Add a top-level error to the response instead of returning nil:
raise GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions"
end
end
The function is used to handle unauthorized objects:
You could refer to the basic authorization guide for more details.
You can prevent access to fields and types from certain users. (They can see them, but if they request them, the request is rejected with an error message.) Use the access:
keyword for this feature.
class AddressType < GraphQL::Schema::Object
# Non-owners may see this type, but they may not request them.
access :owner
# Non-owners may see this field, but they may not request them.
field :telephone_number, String, null: true, access: :owner
end
When a user requests access to an unpermitted field, GraphQL returns an error message. You can customize this error message by providing an unauthorized_fields
hook:
class Schema < GraphQL::Schema
# Override this hook to handle cases when `authorized?` returns false for a field:
def self.unauthorized_field(error)
# Add a top-level error to the response instead of returning nil:
raise GraphQL::ExecutionError, "The field #{error.field.graphql_name} on an object of type #{error.type.graphql_name} was hidden due to permissions"
end
end
The function is used to handle unauthorized fields:
You could refer to the basic authorization guide for more details.
You can hide fields and types from certain users. If they request these types or fields, the error message says that they don’t exist at all.
The view
keyword specifies visibility permission:
class PassportApplicationType < GraphQL::Schema::Object
# Every field on this type is invisible to non-admins
view :admin
# This field is invisible to non-admins
field :social_security_number, String, null: true, view: :admin
# ...
end
NOTE: A new Pundit integration%20is%20available.%20It%20leverages%20GraphQL-Ruby’s%20new%20%5Bbuilt-in%20auth%5D(/authorization/overview) system and has better support for inheritance and customization. If possible, use that one instead!
GraphQL::Pro
includes built-in support for Pundit:
class Schema < GraphQL::Schema
authorization(:pundit)
end
Now, GraphQL will use your *Policy
classes during execution. To find a policy class:
You can also specify a custom policy name. Use the pundit_policy_name:
option, for example:
# A pundit policy:
class TotalBalancePolicy
def initialize(user, obj)
# ...
end
def admin?
# ...
end
end
field :balance, AccountBalanceType, authorize: { role: :admin, pundit_policy_name: "TotalBalancePolicy" }
The permission is defined as a hash with a role:
key and pundit_policy_name:
key. You can pass a hash for view:
and access:
too. For parent_role:
, you can specify a name with parent_pundit_policy_name:
.
For :pundit
, methods will be called with an extra ?
, so
view: :viewer
# => will call the policy's `#viewer?` method
If you put your policies in a namespace, provide that namespace as authorize(..., namespace:)
, for example:
authorize(:pundit, namespace: Policies)
Now, policies will be looked up by name inside Policies::
, for example:
class AccountType < GraphQL::Schema::Object
access :admin # will use Policies::AccountPolicy#admin?
# ...
end
When a resolve function returns an ActiveRecord::Relation
, the policy’s Scope
class will be used if it’s available.
See Scoping for details.
NOTE: A new CanCan integration%20is%20available.%20It%20leverages%20GraphQL-Ruby’s%20new%20%5Bbuilt-in%20auth%5D(/authorization/overview) system and has better support for inheritance and customization. If possible, use that one instead!
GraphQL::Pro
includes built-in support for CanCan:
class Schema < GraphQL::Schema
authorization(:cancan)
end
GraphQL will initialize your Ability
class at the beginning of the query and pass permissions to the #can?
method.
field :phone_number, PhoneNumberType, authorize: :view
# => calls `can?(:view, phone_number)`
For compile-time checks (view
and access
), the object is always nil
.
field :social_security_number, String, null: true, view: :admin
# => calls `can?(:admin, nil)`
When a resolve function returns an ActiveRecord::Relation
, the relation’s accessible_by
method will be used to scope the relation.
See Scoping for details.
By default, GraphQL looks for a top-level Ability
class. You can specify a different class with the ability_class:
option. For example:
class Schema < GraphQL::Schema
authorization(:cancan, ability_class: Permissions::CustomAbility)
end
Now, GraphQL will use Permissions::CustomAbility#can?
to determine permissions.
You can provide custom authorization logic by providing a class:
class Schema < GraphQL::Schema
# Custom authorization strategy class:
authorization(MyAuthStrategy)
end
A custom strategy class must implement #initialize(ctx)
and #allowed?(gate, object)
. Optionally, it may implement #scope(gate, relation)
. For example:
class MyAuthStrategy
def initialize(ctx)
@user = ctx[:custom_user]
end
def allowed?(gate, object)
if object.nil?
# This is a compile-time check,
# so no object is available:
if gate.role == :admin
@user.admin?
else
@user.viewer?
end
else
# This is a runtime check,
# so we can use this specific object
@user.can?(gate.role, object)
end
end
def scope(gate, relation)
# Filter an ActiveRecord::Relation
# according to `@user` and `gate`
# ...
end
end
gate
is the permission setting which responds to:
#level
: where this check occurs: :authorize
, :view
or :access
#role
: the value given to authorize
, view
or access
#owner
: the field or type which has this permission checkobject
is either:
nil
, if the current check is :view
or :access
authorize
For list types, each item of the list is authorized individually.
Database query objects (ActiveRecord::Relation
s and Mongoid::Criteria
s) get special treatment. They get passed to scope handlers so that they can be filtered at database level (eg, SQL WHERE
) instead of Ruby level (eg, .select
).
ActiveRecord::Relation
s can be scoped with SQL by authorization strategies. The Pundit integration uses policy scopes and the CanCan integration uses accessible_by
. Custom authorization strategies can implement #scope(gate, relation)
to apply scoping to ActiveRecord::Relation
s.
Mongoid::Criteria
s are supported in the same way by Pundit policy scopes) and custom strategy’s #scope(gate, relation)
methods, but they aren’t supported by CanCan (which doesn’t support Mongoid, as far as I can tell!).