In the past, we have been asked to include other authentication methods in Devise (e.g. token-based and magic email links). Although it might make sense to include those for some applications, there is no plan to support them in Devise.
But don’t be upset, it turns out you might not need to override Devise’s SessionsController
or monkey patch some of its internals. In this article, you’ll learn how to create a token-based authentication for a JSON API by relying on Warden’s features.
Disclaimers
Warden? Huh?
This article will focus on how to include custom Warden strategies in a Rails application that uses Devise. If you want to know more about Warden strategies, I gave a talk last year at RailsConf that explains them in more details.
Show me the code!
The first part of this article will show how to set up a Rails application using Devise. If you want to skip to the token authentication part, click here.
Setup
Create a new Rails application (this example uses Postgres as the database to take advantage of UUIDs to generate the access tokens):
rails new devise-token-based-auth --database=postgresql
Add the devise
gem to your Gemfile:
gem 'devise'
Now run the Devise generators:
rails generate devise:install
rails generate devise User
We are going to need a column to store the api_token
. For this, we’ll use the pgcrypto extension’s gen_random_uuid()
function.
First, create a migration to enable the pgcrypto
extension:
rails generate migration enable_pgcrypto_extension
Now edit the migration to call the #enable_extension
method:
class EnablePgcryptoExtension < ActiveRecord::Migration[5.2]
def change
enable_extension 'pgcrypto'
end
end
The database is now able to use pgcrypto
‘s functions. Now we can create a migration to add the api_token
column:
rails generate migration add_api_token_to_users
Now edit the migration like the one below. Notice the default is set to gen_random_uuid()
:
class AddApiTokenToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :api_token, :string, default: -> { 'gen_random_uuid()' }
add_index :users, :api_token, unique: true
end
end
Don’t forget to create the database and run the migrations:
rails db:create db:migrate
The next step is to create a user using rails console
and grab its api_token
:
rails console
Running via Spring preloader in process 60784
Loading development environment (Rails 5.2.2)
irb(main):001:0> user = User.create!(email: 'bruce@wayne.com', password: '123123')
=> #
irb(main):002:0> user.reload.api_token
=> "a4839b85-4c96-4f22-96f1-c2568e5d6a7f"
It’s time to create the Warden strategy now!
The Api Token Strategy
Create a file app/strategies/api_token_strategy.rb
with the following content:
class ApiTokenStrategy < Warden::Strategies::Base
def valid?
api_token.present?
end
def authenticate!
user = User.find_by(api_token: api_token)
if user
success!(user)
else
fail!('Invalid email or password')
end
end
private
def api_token
env['HTTP_AUTHORIZATION'].to_s.remove('Bearer ')
end
end
In short, the strategy tries to find a user for the token sent in the Authorization
header. If it does, it signs the user in. Otherwise, it returns an error. If you are not familiar with the success!
and fail!
methods, watch the talk on the start of the blog post to get a sense on how Warden works.
Warden needs to know about this strategy. Create a file config/initializers/warden.rb
with the following code:
Warden::Strategies.add(:api_token, ApiTokenStrategy)
This allows Warden to recognise that it should call the ApiTokenStrategy
when it receives the :api_token
symbol.
Authenticating a user
Now it’s time to use the strategy. Create an UsersController
that renders the current_user
in JSON:
class UsersController < ApplicationController
def show
render json: current_user.to_json
end
end
Don’t forget to add a route for this controller action. Open config/routes.rb
in your editor and include the following:
Rails.application.routes.draw do
devise_for :users
resource :user, only: :show
end
To require authentication in the controller, the method #authenticate!
should be called passing the desired strategy as a parameter:
class UsersController < ApplicationController
def show
warden.authenticate!(:api_token)
render json: current_user.to_json
end
end
You can see that this works using a simple curl
request:
curl http://localhost:3000/user -H 'Authorization: Bearer a4839b85-4c96-4f22-96f1-c2568e5d6a7f'
{"id":3,"email":"bruce@wayne.com","created_at":"2018-12-26T13:45:37.473Z","updated_at":"2018-12-26T13:45:37.473Z","api_token":"a4839b85-4c96-4f22-96f1-c2568e5d6a7f"}
It is also possible to define :api_token
as a default strategy so that it’s called when no strategy is passed as a parameter. Add the following code in the config/initializers/devise.rb
file:
Devise.setup do |config|
# The secret key used by Devise. Devise uses this key to generate...
config.warden do |manager|
manager.default_strategies(scope: :user).unshift :api_token
end
# ==> Mountable engine configurations...
end
This will add the :api_token
strategy in the first position, followed by Devise’s default strategies (:rememberable
and :database_authenticatable
).
Now it’s possible to use Devise’s #authenticate_user!
helper, and the :api_token
will still be used:
class UsersController < ApplicationController
before_action :authenticate_user!
def show
render json: current_user.to_json
end
end
Summary
And… we’re done! The focus here was to show how to include custom Warden strategies in a Rails application. The example was straightforward but you can follow this structure to create custom authentication logic to suit your application’s needs.
The entire application used in this article can be found in GitHub.