Devise API
The devise-api gem is a convenient way to add authentication to your Ruby on Rails application using the devise gem. It provides support for access tokens and refresh tokens, which allow you to authenticate API requests and keep the user's session active for a longer period of time on the client side. It can be installed by adding the gem to your Gemfile, running migrations, and adding the :api module to your devise model. The gem is fully configurable, allowing you to set things like token expiration times and token generators.
Here's how it works:
- When a user logs in to your Rails application, the
devise-api
gem generates an access token and a refresh token. - The access token is included in the API request headers and is used to authenticate the user on each subsequent request.
- The refresh token is stored on the client side (e.g. in a browser cookie or on a mobile device) and is used to obtain a new access token when the original access token expires.
- This allows the user to remain logged in and make API requests without having to constantly re-enter their login credentials.
Overall, the devise-api
gem is a useful tool for adding secure authentication to your Ruby on Rails application.
Installation
Install the gem and add to the application's Gemfile by executing:
$ bundle add devise-api
Or add the following line to the application's Gemfile:
gem 'devise-api', github: 'nejdetkadir/devise-api', branch: 'main'
If bundler is not being used to manage dependencies, install the gem by executing:
gem install devise-api
After that, you need to generate relevant migrations and locales by executing:
$ rails generate devise_api:install
This will introduce two changes:
- Locale files in
config/locales/devise_api.en.yml
- Migration file in
db/migrate
to create devise api tokens table
Now you're ready to run the migrations:
$ rails db:migrate
Finally, you need to add :api
module to your devise model. For example:
class User < ApplicationRecord
devise :database_authenticatable,
:registerable,
:recoverable,
:rememberable,
:validatable,
:api # <--- Add this module
end
Your user model is now ready to use devise-api
gem. It will draw routes for token authenticatable and token refreshable.
Prefix | Verb | URI Pattern | Controller#Action |
---|---|---|---|
revoke_user_tokens | POST | /users/tokens/revoke | devise/api/tokens#revoke |
refresh_user_tokens | POST | /users/tokens/refresh | devise/api/tokens#refresh |
sign_up_user_tokens | POST | /users/tokens/sign_up | devise/api/tokens#sign_up |
sign_in_user_tokens | POST | /users/tokens/sign_in | devise/api/tokens#sign_in |
info_user_tokens | GET | /users/tokens/info | devise/api/tokens#info |
You can look up the example requests.
Configuration
devise-api
is a full configurable gem. You can configure it to your needs. Here is a basic usage example:
# config/initializers/devise.rb
Devise.setup do |config|
config.api.configure do |api|
# Access Token
api.access_token.expires_in = 1.hour
api.access_token.expires_in_infinite = ->(_resource_owner) { false }
api.access_token.generator = ->(_resource_owner) { Devise.friendly_token(60) }
# Refresh Token
api.refresh_token.enabled = true
api.refresh_token.expires_in = 1.week
api.refresh_token.generator = ->(_resource_owner) { Devise.friendly_token(60) }
api.refresh_token.expires_in_infinite = ->(_resource_owner) { false }
# Sign up
api.sign_up.enabled = true
api.sign_up.extra_fields = []
# Authorization
api.authorization.key = 'Authorization'
api.authorization.scheme = 'Bearer'
api.authorization.location = :both # :header or :params or :both
api.authorization.params_key = 'access_token'
# Base classes
api.base_token_model = 'Devise::Api::Token'
api.base_controller = '::DeviseController'
# After successful callbacks
api.after_successful_sign_in = ->(_resource_owner, _token, _request) { }
api.after_successful_sign_up = ->(_resource_owner, _token, _request) { }
api.after_successful_refresh = ->(_resource_owner, _token, _request) { }
api.after_successful_revoke = ->(_resource_owner, _token, _request) { }
# Before callbacks
api.before_sign_in = ->(_params, _request, _resource_class) { }
api.before_sign_up = ->(_params, _request, _resource_class) { }
api.before_refresh = ->(_token_model, _request) { }
api.before_revoke = ->(_token_model, _request) { }
end
end
Routes
You can configure the tokens routes with the orginally devise_for
method. For example:
# config/routes.rb
Rails.application.routes.draw do
devise_for :customers,
controllers: { tokens: 'customers/api/tokens' }
end
Usage
devise-api
module works with :lockable
and :confirmable
modules. It also works with :trackable
module.
devise-api
provides a set of controllers and helpers to help you implement authentication in your Rails application. Here's a quick overview of the available controllers and helpers:
-
Devise::Api::TokensController - This controller is responsible for generating access tokens and refresh tokens. It also provides actions for refreshing access tokens and revoking refresh tokens.
-
Devise::Api::Token - This model is responsible for storing access tokens and refresh tokens in the database.
-
Devise::Api::Responses::ErrorResponse - This class is responsible for generating error responses. It also provides a set of error types and helpers to help you implement error responses in your Rails application.
-
Devise::Api::Responses::TokenResponse - This class is responsible for generating token responses. It also provides actions for generating access tokens and refresh tokens for each action.
Overriding Responses
You can prepend your decorators to the response classes to override the default responses. For example:
# app/lib/devise/api/responses/token_response_decorator.rb
module Devise::Api::Responses::TokenResponseDecorator
def body
return default_body.merge({ roles: resource_owner.roles })
end
end
Then you need to load and prepend your decorator to the response class. For example:
# config/initializers/devise.rb
require 'devise/api/responses/token_response_decorator' # Either do this or autoload the lib directory
Devise.setup do |config|
end
Devise::Api::Responses::TokenResponse.prepend Devise::Api::Responses::TokenResponseDecorator
Using helpers
devise-api
provides a set of helpers to help you implement authentication in your Rails application. Here's a quick overview of the available helpers:
Example:
# app/controllers/api/v1/orders_controller.rb
class Api::V1::OrdersController < YourBaseController
skip_before_action :verify_authenticity_token, raise: false
before_action :authenticate_devise_api_token!
def index
render json: current_devise_api_user.orders, status: :ok
end
def show
devise_api_token = current_devise_api_token
render json: devise_api_token.resource_owner.orders.find(params[:id]), status: :ok
end
end
Using devise base services
devise-api
provides a set of base services to help you implement authentication in your Rails application. Here's a quick overview of the available services:
- Devise::Api::BaseService - This service is useful for creating and updating resources. It is inherited by the following gems.
- dry-monads
- dry-types
- dry-initializer
You can create a service by inheriting the Devise::Api::BaseService
class. For example:
# app/services/devise/api/tokens_service/v2/create.rb
module Devise::Api::TokensService::V2
class Create < Devise::Api::BaseService
option :params, type: Types::Hash, reader: true
option :resource_class, type: Types::Class, reader: true
def call
...
Success(resource)
end
end
end
Then you can call the service in your controller. For example:
# app/controllers/api/v1/tokens_controller.rb
class Api::V1::TokensController < YourBaseController
skip_before_action :verify_authenticity_token, raise: false
def create
service = Devise::Api::TokensService::V2::Create.call(params: params, resource_class: Customer || resource_class)
if service.success?
render json: service.success, status: :created
else
render json: service.failure, status: :unprocessable_entity
end
end
end
Example API requests
Sign in
curl --location --request POST 'http://127.0.0.1:3000/users/tokens/sign_in' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "test@development.com",
"password": "123456"
}'
Sign up
curl --location --request POST 'http://127.0.0.1:3000/users/tokens/sign_up' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "test@development.com",
"password": "123456"
}'
Refresh token
curl --location --request POST 'http://127.0.0.1:3000/users/tokens/refresh' \
--header 'Authorization: Bearer <refresh_token>'
Revoke
curl --location --request POST 'http://127.0.0.1:3000/users/tokens/revoke' \
--header 'Authorization: Bearer <access_token>'
Info
curl --location --request GET 'http://127.0.0.1:3000/users/tokens/info' \
--header 'Authorization: Bearer <access_token>'
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake rspec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/nejdetkadir/devise-api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Devise::Api project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.