GraphQL interface on top devise_token_auth
GraphQL interface on top of the Devise Token Auth (DTA) gem.
Graphql-Devise heavily relies on 3 gems:
This gem provides a GraphQL interface on top of DTA which is designed for REST APIs. Features like token management, token expiration and everything up until using the actual GraphQL schema is still controlled by DTA. For that reason the gem’s generator invokes DTA and Devise generators and creates initializer files for each one of them.
We strongly recommend getting familiar with DTA documentation to use this gem to its full potential.
More configuration details available in configuration section
Add this line to your application’s Gemfile:
gem 'graphql_devise'
And then execute:
$ bundle
Graphql Devise generator will execute Devise
and Devise Token Auth
generators to setup the gems in your project. You can customize them to your needs using their initializer files(one per gem) as usual.
$ bundle exec rails generate graphql_devise:install
The generator accepts 2 params and 1 option:
user_class
: Model name in which Devise
modules will be included. This uses a find or create
strategy. Defaults to User
.mount_path
: Path in which the dedicated graphql schema for devise will be mounted. Defaults to /graphql_auth
.--mount
: This options is available starting from v0.12.0
, it allows you to mount the operations in your own schema instead of a dedicated one. When provided mount_path
param is ignored.To configure the gem to use a separate schema, the generator will use user_class
and mount_path
params.
The route will be mounted in config/routes.rb
. For instance the executing:
$ bundle exec rails g graphql_devise:install Admin api/auth
Will do the following:
Devise
install generatorDevise Token Auth
install generator with Admin
and api/auth
as params
Admin
modeldevise
modules to Admin
modelconfig/routes.rb
mount_graphql_devise_for Admin, at: 'api/auth'
Admin
could be any model name you are going to be using for authentication,
and api/auth
could be any mount path you would like to use for auth.
Important
at
option in the mount_graphql_devise_for
method in the config/routes.rb
file. If no at
option is provided, the route will be /graphql_auth
.--mount
option if you want to use a separate route and schema.To configure the gem to use an existing GQL schema use the --mount
option.
For instance the executing:
$ bundle exec rails g graphql_devise:install Admin --mount MySchema
Will do the following:
Devise
install generatorDevise Token Auth
install generator with Admin
and api/auth
as params
Admin
modeldevise
modules to Admin
modelSchemaPlugin
to the specified schema.Important
--mount
option the mount_path
param is ignored.app/graphql/
directory. We are expecting the name of the file is the same as the as the one passed in the mount option transformed with underscore
. In the example, passing MySchema
, will try to find the file app/graphql/my_schema.rb
.GraphqlDevise operations can be used in two ways:
mount_graphql_devise_for
helper in the routes file.Creating a separate schema is the default option, the generator will do that by default.
You can mount this gem’s GraphQL auth schema in your routes file like this:
# config/routes.rb
Rails.application.routes.draw do
mount_graphql_devise_for(
User,
at: 'api/v1',
authenticatable_type: Types::MyCustomUserType,
operations: {
login: Mutations::Login
},
skip: [:register],
# optional, use only if you need a specific base controller to mount the new actions
base_controller: ApiController,
additional_mutations: {
# generates mutation { adminUserSignUp }
admin_user_sign_up: Mutations::AdminUserSignUp
},
additional_queries: {
# generates query { publicUserByUuid }
public_user_by_uuid: Resolvers::UserByUuid
}
)
end
This can be done using the generator or manually.
The second argument of the mount_graphql_devise
method is a hash of options where you can
customize how the queries and mutations are mounted into the schema. For a list of available
options go here
Starting with v0.12.0
you can mount the GQL operations provided by this gem into an
existing schema in your app.
# app/graphql/dummy_schema.rb
class DummySchema < GraphQL::Schema
# It's important that this line goes before setting the query and mutation type on your
# schema in graphql versions < 1.10.0
use GraphqlDevise::SchemaPlugin.new(
query: Types::QueryType,
mutation: Types::MutationType,
resource_loaders: [
GraphqlDevise::ResourceLoader.new(User, only: [:login, :confirm_registration_with_token])
]
)
mutation(Types::MutationType)
query(Types::QueryType)
end
The example above describes just one of the possible scenarios you might need.
The second argument of the GraphqlDevise::ResourceLoader
initializer is a hash of
options where you can customize how the queries and mutations are mounted into the schema.
For a list of available options go here.
It’s important to use the plugin in your schema before assigning the mutation and query type to
it in graphql versions < 1.10.0
. Otherwise the auth operations won’t be available.
You can provide as many resource loaders as you need to the resource_loaders
option, and each
of those will be loaded into your schema. These are the options you can initialize the
SchemaPlugin
with:
query
: This param is mandatory unless you skip all queries via the resource loaderQueryType
you provide to the query
methodmutation
: This param mandatory unless you skip all mutations via the resource loaderMutationType
you provide to the mutation
methodresource_loaders
: This is an optional array of GraphqlDevise::ResourceLoader
instances.authenticate_default
: This is a boolean value which is true
by default. This valuetrue
meansauthenticate: false
option on the field. false
means your root level fields won’t requireauthenticate: true
option on the field.unauthenticated_proc
: This param is optional. Here you can provide a proc that receivesGraphQL::ExecutionError
will bepublic_introspection
: The introspection query is a very useful GQL resource that providespublic_introspection
option. This optionBoth the mount_graphql_devise_for
method and the GraphqlDevise::ResourceLoader
class
take the same options. So, wether you decide to mount this gem in a separate route
from your main application’s schema or you use our GraphqlDevise::SchemaPlugin
to load
this gem’s auth operation into your schema, these are the options you can provide as a hash.
# Using the mount method in your config/routes.rb file
mount_graphql_devise_for(User, {})
# Providing options to a GraphqlDevise::ResourceLoader
GraphqlDevise::ResourceLoader.new(User, {})
at
: Route where the GraphQL schema will be mounted on the Rails server.POST /api/v1/graphql_auth
and GET /api/v1/graphql_auth
./graphql_auth
. This option only works if you are using the mount method.operations
: Specifying this is optional. Here you can override defaultsuper
, exampleauthenticatable_type
: By default, the gem will add an authenticatable
field to every mutationauthenticatable
type to every query. Gem will try to use Types::<model>Type
byTypes::UserType
and every query and mutationbase_controller
: Specifying this is optional. By default the controller used to mount the route isGraphqlDevise::ApplicationController
which inherits from ActionController::API
or ActionController::Base
Devise
’s base_controller
config but inskip
: An array of the operations that should not be available in the authentication schema. All these operations areonly
: An array of the operations that should be available in the authentication schema. The skip
and only
options areadditional_mutations
: Here you can add as many mutations as youadditional_queries
: Here you can add as many queries as you need,Additional mutations and queries will be added to the schema regardless
of other options you might have specified like skip
or only
.
Additional queries and mutations is usually a good place for other
operations on your schema that require no authentication (like sign_up).
Also by adding them through the mount method, your mutations and
resolvers can inherit from our base mutation
or base resolver
respectively, to take advantage of some of the methods provided by devise
just like with devise_scope
The following is a list of the symbols you can provide to the operations
, skip
and only
options of the mount method:
:login
:logout
:register
:update_password_with_token
:send_password_reset_with_token
:resend_confirmation_with_token
:confirm_registration_with_token
Just like with Devise and DTA, you need to include a module in your authenticatable model,
so with our example, your user model will have to look like this:
# app/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable,
:registerable,
:recoverable,
:rememberable,
:trackable,
:lockable,
:validatable,
:confirmable
# including after calling the `devise` method is important.
include GraphqlDevise::Authenticatable
end
The install generator can do this for you if you specify the user_class
option.
See Installation for details.
We want reconfirmable in this gem to work separately
from DTA’s or Devise (too much complexity in the model based on callbacks).
Email reconfirmation is supported just like in Devise and DTA, but we want reconfirmable
in this gem to work on model basis instead of having a global configuration like in Devise.
For this reason Devise’s global reconfirmable
setting is ignored.
For a resource to be considered reconfirmable it has to meet 2 conditions:
:confirmable
module.unconfirmed_email
column in the resource’s table.In order to trigger the reconfirmation email in a reconfirmable resource, you simply need
to call a different update method on your resource,update_with_email
.
When the resource is not reconfirmable or the email is not updated, this method behaves exactly
the same as ActiveRecord’s update
.
update_with_email
requires one additional attribute when email will change or an error
will be raised:
confirmation_url
: The full url of your client application. The confirmation email will contain this url plusconfirmRegistrationWithToken
with the given token onSo, it’s up to you where you require confirmation of changing emails.
Here’s a demonstration on the method usage:
user.update_with_email(
name: 'New Name',
email: '[email protected]',
confirmation_url: 'https://google.com'
)
The approach of this gem is a bit different from DeviseTokenAuth. We have placed our templates in app/views/graphql_devise/mailer
,
so if you want to change them, place yours on the same dir structure on your Rails project. You can customize these two templates:
app/views/graphql_devise/mailer/confirmation_instructions.html.erb
app/views/graphql_devise/mailer/reset_password_instructions.html.erb
The main reason for this difference is just to make it easier to have both Standard Devise
and this gem running at the same time.
Check these files to see the available helper methods you can use in your views.
GraphQL Devise supports locales. For example, the graphql_devise.confirmations.send_instructions
locale setting supports the %{email}
variable in case you would like to include it in the resend confirmation instructions for the user. Take a look at our locale file to see all of the available messages.
Keep in mind that if your app uses multiple locales, you should set the I18n.locale
accordingly. You can learn how to do this here.
When mounting the operation is in you own schema instead of a dedicated one, you will need to authenticate users in your controllers, just like in DTA. There are 2 alternatives to accomplish this.
This authentication mechanism sets the resource by token in the controller, or it doesn’t if credentials are invalid.
You simply need to pass the return value of our gql_devise_context
method in the context of your
GQL schema execution like this:
# app/controllers/my_controller.rb
class MyController < ApplicationController
include GraphqlDevise::SetUserByToken
def my_action
result = DummySchema.execute(params[:query], context: gql_devise_context(User))
render json: result unless performed?
end
end
gql_devise_context
receives as many models as you need to authenticate in the request, like this:
# app/controllers/my_controller.rb
class MyController < ApplicationController
include GraphqlDevise::SetUserByToken
def my_action
result = DummySchema.execute(params[:query], context: gql_devise_context(User, Admin))
render json: result unless performed?
end
end
Internally in your own mutations and queries a key current_resource
will be available in
the context if a resource was successfully authenticated or nil
otherwise.
Keep in mind that sending multiple models to the gql_devise_context
method means that depending
on who makes the request, the context value current_resource
might contain instances of the
different models you provided.
Note: If for any reason you need more control over how users are authenticated, you can use the authenticate_model
method anywhere in your controller. The method will return the authenticated resource or nil if authentication fails.
It will also set the instance variable @resource
in the controller.
Please note that by using this mechanism your GQL schema will be in control of what queries are
restricted to authenticated users and you can only do this at the root level fields of your GQL
schema. Configure the plugin as explained here
so this can work.
Whether you setup authentications as a default in the plugin, or you do it at the field level,
these are the options you can use:
current_resource
is not .present?
, query will return an authentication error.current_resource
as the only argument if current_resource
is .present?
. If return value of the callable object is false, query will return an authentication error.In your main app’s schema this is how you might specify if a field needs to be authenticated or not:
module Types
class QueryType < Types::BaseObject
# user field used the default set in the Plugin's initializer
field :user, resolver: Resolvers::UserShow
# this field will never require authentication
field :public_field, String, null: false, authenticate: false
# this field requires authentication
field :private_field, String, null: false, authenticate: true
# this field requires authenticated users to also be admins
field :admin_field, String, null: false, authenticate: ->(user) { user.admin? }
end
end
Here is a list of the available mutations and queries assuming your mounted model is User
.
If you are using the schema plugin, you can require authentication before doing an introspection query by modifying the public_introspection
option of the plugin. Check the plugin config section for more information.
Operation | Description | Example |
---|---|---|
login | This mutation has a second field by default. credentials can be fetched directly on the mutation return type.Credentials are still returned in the headers of the response. |
userLogin(email: String!, password: String!): UserLoginPayload |
logout | requires authentication headers. Deletes current session if successful. | userLogout: UserLogoutPayload |
register | The parameter confirmUrl is optional unless you are using the confirmable plugin from Devise in your resource ’s model. If you have confirmable set up, you will have to provide it unless you have config.default_confirm_success_url set in config/initializers/devise_token_auth.rb . |
userRegister(email: String!, password: String!, passwordConfirmation: String!, confirmUrl: String): UserRegisterPayload |
sendPasswordResetWithToken | Sends an email to the provided address with a link to reset the password of the resource. First step of the most recently implemented password reset flow. | userSendPasswordResetWithToken(email: String!, redirectUrl: String!): UserSendPasswordResetWithTokenPayload |
updatePasswordWithToken | Uses a resetPasswordToken to update the password of a resource. Second and last step of the most recently implemented password reset flow. |
userSendPasswordResetWithToken(resetPasswordToken: String!, password: String!, passwordConfirmation: String!): UserUpdatePasswordWithTokenPayload |
resendConfirmationWithToken | The UserResendConfirmationWithTokenPayload will return a message: String! that can be used to notify a user what to do after the instructions were sent to them. Email will contain a link to the provided confirmUrl and a confirmationToken query param. |
userResendConfirmationWithToken(email: String!, confirmUrl: String!): UserResendConfirmationWithTokenPayload |
This gem supports two password recovery flows. The most recently implemented is preferred and
requires less steps. More detail on how it works can be found
here.
As mentioned in the introduction there are many configurations that will change how this gem behaves. You can change
this values on the initializer files generated by the installer.
The generated initializer file config/initializers/devise_token_auth.rb
has all the available options documented
as comments. You can also use
DTA’s docs as a reference.
In this section the most important configurations will be highlighted.
false
. This will allow you to store thetrue
means that tokens will change on each request you make, and the new values will be returned1.month
, 2.weeks
, 1.hour
, etc.Note: Remember this gem adds a layer on top of DTA, so some configurations might not apply.
The generated initializer file config/initializers/devise_token_auth.rb
has all the available options documented
as comments. You can also use
Devise’s docs as a reference.
In this section the most important configurations will be highlighted.
'[email protected]'
.[:email]
will make email field case insensitive on login, sign up, etc.Note: Remember this gem adds a layer on top of Devise, so some configurations might not apply.
This gem now supports GraphQL Ruby v2.
There’s one manual step you need to take in order for this to work.
You need a custom field_class
in your MutationType
and QueryType
. If you don’t have one setup already, you can simply add the one
this gem provides, like this:
module Types
class MutationType < BaseObject
field_class GraphqlDevise::Types::BaseField
end
end
module Types
class QueryType < Types::BaseObject
field_class GraphqlDevise::Types::BaseField
end
end
If you already have a field_class
defined in any of your types, the only thing you need to do is add another kwarg
to that class initializer (authenticate
) and make that value available through an attribute reader.
The next example is this gem’s implementation of a custom class, but you can implement your own however you see fit
as long as you expose an authenticate
public method with the value that was passed to the initializer.
module GraphqlDevise
module Types
class BaseField < GraphQL::Schema::Field
def initialize(*args, authenticate: nil, **kwargs, &block)
@authenticate = authenticate
super(*args, **kwargs, &block)
end
attr_reader :authenticate
end
end
end
GraphQL-Ruby >= 1.9.0
includes a new runtime module which you may use for your schema.
Eventually, it will become the default. You can read more about it
here.
This gem supports schemas using the interpreter and it is recommended as it introduces several
improvements which focus mainly on performance.
The DeviseTokenAuth gem allows experimental use of the standard Devise gem to be configured at the same time, for more
information you can check this answer here.
This gem supports the same and should be easier to handle email templates due to the fact we don’t override
standard Devise templates.
Full list of changes in CHANGELOG.md
We will continue to improve the gem and add better docs.
We will continue to build better docs for the gem after this first release, but in the mean time
you can use our specs to better understand how to use the gem.
Also, the dummy app used in our specs will give you
a clear idea on how to configure the gem on your Rails application.
If you’d like to support our work, you are welcome to do so!
bc1qntlmyl24wuf6y5jyn2vg8kduss57dwtyrcflyq
addr1q8e8cjzutzptcrfgjgsjl3k4t4xy5ucrmkf2dmq9qn966q8saucty53avujfc9yu9vfk7266auhdx9fz4fsryzeagqds893nfw
Bug reports and pull requests are welcome on GitHub at https://github.com/graphql-devise/graphql_devise.
The gem is available as open source under the terms of the MIT License.