22.10.2017 - Read in 7 min.
Live without devise: a simple warden authentication service
22.10.2017 - Read in 7 min.
Nowadays, almost every Rails project has authentication delivered by a powerful devise gem. But, in some circumstances using this gem may be just like shooting a fly with a cannon.

Nowadays, almost every Rails project has authentication delivered by a powerful devise gem. But, in some circumstances using this gem may be just like shooting a fly with a cannon. For instance, you want to create a simple API that gives access to resources via the authorization access token sent in the header. In this case, after sign in, a user receives an initial token and then uses it in a first request. A response to this request returns a new access token in its header and so on. Pretty straightforward isn’t it. But in the pure devise gem it’s quite hard to achieve. You have to look into the gems controllers to inject your authorization logic. Also, a solid test suite should be created to ensure that everything works fine. So, why not create a simple authorization service and abandon devise?
WARDEN – THE SPINE OF DEVISE
The devise gem is basically based on a warden gem, which gives an opportunity to build authorization direct on a Ruby Rack Stack. This gem is pretty straightforward and well documented. Warden fetches a request data and checks if the request includes valid credentials, according to a defined strategy. If a user has access, warden establishes the request sender in an application context and then passes the request to the next part of Rails Rack Middleware Stack. If verification fails, it calls a special failure procedure, which deals with a no access case.
Let’s create a plain Cars API
to demonstrate warden capabilities.
$ rails new car_api --api
We have to add warden and rspec-rails gems to our project.
# Gemfile
gem 'warden'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'rspec-rails'
end
Bundle it and install rspec:
$ bundle
$ bundle exec rails g rspec:install
We will need two models: User
, which makes requests and wants to get access to API and Car
, that plays a role of a restricted resource.
$ bundle exec rails g model User name token:uniq
$ bundle exec rails g model Car name brand user:belongs_to
$ bundle exec rake db:migrate
We’ve done our model layer, so it’s time to add some specs for login endpoint:
# spec/requests/api/v1/sing_in_spec.rb
require 'rails_helper'
RSpec.describe 'Sing in', type: :request do
describe 'GET /api/v1/login' do
let!(:user) { User.create(id: 'Stive') }
it "returns user’s access token" do
post '/api/v1/login', params: { id: user.id }
expect(user.token).to be_present
expect(JSON.parse(response.body)).to eq({ 'token' => user.token })
end
end
end
Let’s make it simple. A client sends id, then in a response, he will receive a first one-time usage token.
Here is an implementation of a sing in functionality:
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
post 'login' => 'auth#login'
end
end
end
# app/controllers/api/v1/auth_controller.rb
class Api::V1::AuthController < ApplicationController
def login
render json: User.find(params[:id]).attributes.slice('token')
end
end
If we run our ‘request sign in spec’, it will fail:
The reason of a failure is a lack of user’s secure token. Fortunately, Rails Framework has built-in functionality: ActiveRecord::SecureToken. It allows generating token like values.
Let’s add some specs for the User
model and secure token declaration in the model class.
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
it 'has secure token' do
expect(User.create(name: 'Bob').token).to be_present
end
end
# app/models/user.rb
class User < ApplicationRecord
has_secure_token
has_many :cars
end
Now, all specs should pass.
We have the access token, so let’s make some request to protected content. Before we start implementing devise functionality, we should add specs in TDD manner.
# spec/requests/api/v1/cars_spec.rb
require 'rails_helper'
RSpec.describe 'Cars management', type: :request do
let!(:user) { User.create(name: 'Bill') }
let!(:car) { Car.create(name: 'Passat', brand: 'VW', user: user) }
describe 'GET /api/v1/cars' do
it 'is protected content' do
get '/api/v1/cars'
expect(response.status).to eq 401
end
it 'returns cars list for a user with access' do
get '/api/v1/cars', headers: { access_token: user.token }
expect(response.status).to eq 200
expect(JSON.parse(response.body)).to eq [car.attributes.slice('id', 'name', 'brand').as_json]
end
it 'cannot use twice same token' do
get '/api/v1/cars', headers: { access_token: user.token }
expect(response.status).to eq 200
last_valid_token = response.header['Access-Token']
get '/api/v1/cars', headers: { access_token: user.token }
expect(response.status).to eq 401
expect(response.header['Access-Token']).to be_nil
get '/api/v1/cars', headers: { access_token: last_valid_token }
expect(response.status).to eq 200
end
end
describe 'POST /api/v1/cars' do
let(:car_params) { { 'brand' => 'Honda', 'name' => 'Civic' } }
it 'is protected content' do
get '/api/v1/cars', params: { car: car_params }
expect(response.status).to eq 401
end
it 'creates a car' do
post '/api/v1/cars', params: { car: car_params }, headers: { access_token: user.token }
expect(response.status).to eq 201
new_car = JSON.parse(response.body)
expect(new_car.slice('id', 'name', 'brand')).to eq(car_params.merge({'id' => Car.last.id}))
expect(user.cars.count).to eq 2
end
end
end
We’ll add a controller for a car resource.
# app/controllers/api/v1/cars_controller.rb
class Api::V1::CarsController < ApplicationController
prepend_before_action :authenticate!
def index
render json: Car.select(:id, :name, :brand)
end
def create
car = current_user.cars.create(car_params)
render json: car, status: :created
end
protected
def car_params
params.require(:car).permit(:brand, :name)
end
end
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :cars, only: [:index, :create]
post 'login' => 'auth#login'
end
end
end
If we run specs suite now, we get an authenticate! method missing error. Also, we don’t have a current_user method defined. So, it’s time to start the implementation of a simple warden authentication service. The warden gem is a part of Rails Rack Stack, so we have to modify config/application.rb file to add it to the Rails middleware.
# config/application.rb
..
..
config.api_only = true
config.middleware.use Warden::Manager do |manager|
manager.default_strategies :token
manager.failure_app = Proc.new { |env| ['401', {'Content-Type' => 'application/json'}, { error: 'Unauthorized', code: 401 }] }
end
In our case Warden::Manager gets two configuration options:
default_stregies
allows setting your own algorithm of authentication, which warden will use. In warden’s philosophy users can define multiple strategies, which will be used by the warden in certain cases. We’ll also create one strategy later.failure_app
option is a procedure that warden will call when authentication fails. The proc block as a parameter gets current Ruby Rack Stack environment, where you can find data related to HTTP request that has been made. The block returns three elements array, which represents Rails Rack response. The first element is response status, in our case 401, that means the user has no valid credentials to gain a resource. The second element is a response header, in our case, we are setting a response content type to JSON. The last element represents a response body, in our case, an authorization error message.
After informing Rails application, that we are using warden, we have to define our token strategy.
# lib/authentication/token_strategy.rb
require 'warden'
module Authentication
class TokenStrategy < Warden::Strategies::Base
def valid?
access_token.present?
end
def authenticate!
user = User.find_by_token(access_token)
if user.nil?
fail!('Could not log in')
else
user.regenerate_token
success!(user)
end
end
private
def access_token
@access_token ||= request.get_header('access_token')
end
end
end
A strategy class inherits from Warden::Strategies::Base
. Each new strategy has to implement an authentication! method. The valid?
method is optional, by default it returns true
. It checks that authentication may be run. In our token strategy, we try to find a user by the token. If we don’t find one, we invoke warden fail!
method, which triggers failure app, that we defined. But, if the user exists we create a new token for that user and call warden success!
method. Then the user found by the token can be accessed by warden[‘user’]
in any controller. Now, we have to tell warden gem, that we defined a new strategy:
# config/initializers/warden.rb
Warden::Strategies.add(:token, Authentication::TokenStrategy)
Also, we need to include the strategy class in config/application.rb
:
# config/application.rb
..
..
require_relative '../lib/authentication/token_strategy.rb'
Bundler.require(*Rails.groups)
module CarApi
class Application < Rails::Application
config.load_defaults 5.1
config.api_only = true
config.autoload_paths += %W( #{config.root}/lib )
config.middleware.use Warden::Manager do |manager|
manager.default_strategies :token
manager.failure_app = Proc.new { |env| ['401', {'Content-Type' => 'application/json'}, { error: 'Unauthorized', code: 401 }] }
end
end
end
To finish our authorization service we have to set a new user’s token in the response, that the user might use in the next request. So, let’s add a new layer to the Rails Rack Stack.
# lib/authentication/set_response_token.rb
module Authentication
class SetResponseToken
def initialize app
@app = app
end
def call env
res = @app.call(env)
if res[0] < 300 && !skipp_request(env)
res[1]["Access-Token"] = env['warden'].user.token
end
res
end
private
def skipp_request(env)
env['REQUEST_URI'] =~ /\/login$/
end
end
end
This class is a classic Rack Middleware class. A constructor of this class, as an argument receives the Rails application. The call method checks response status and verifies if the response is not from a sign in request. Then if conditions pass, it adds to the response header user’s access token. To make it work, we have to add this class to the Rails application middleware.
# config/application.rb
..
..
require_relative '../lib/authentication/token_strategy.rb'
require_relative '../lib/authentication/set_response_token.rb'
Bundler.require(*Rails.groups)
module CarApi
class Application < Rails::Application
config.load_defaults 5.1
config.api_only = true
config.autoload_paths += %W( #{config.root}/lib )
config.middleware.use Warden::Manager do |manager|
manager.default_strategies :token
manager.failure_app = Proc.new { |env| ['401', {'Content-Type' => 'application/json'}, { error: 'Unauthorized', code: 401 }] }
end
config.middleware.use Authentication::SetResponseToken
end
end
Finally, we can implement authenticate!
and current_user
methods.
# lib/authentication/helper_methods.rb
module Authentication
module HelperMethods
def authenticate!
request.env['warden'].authenticate!
end
def current_user
request.env['warden'].user
end
end
end
Let’s make those methods accessible to all controllers in our application.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include Authentication::HelperMethods
end
Now, if we run specs, we should get all green ones.
Success! We finished the authorization service.
CONCLUSION
The warden gem is a simple and powerful tool that allows building custom authentication in Ruby web applications. A solid understating how does it work, not only allows you to create your own authentication services but also allows extending devise gem by adding to it new authentication strategies.
Share