When it comes to Rails authentication, devise is the go-to gem: “Devise is a flexible authentication solution for Rails based on Warden”
But sometimes, all the flexibility and convenience of Devise might not be necessary.
Recently, I had to add some custom authentication to a small rails-api application.
It was a really lightweight, didn’t have a database and the authentication was done against an external service.
Devise wasn’t going to work out of the box so I decided to dive one layer deeper and work directly with Warden.
Warden performs authentication at the rack middleware level. It does this by using Strategies.
Warden only provides a Base strategy from which other must inherit.
Before getting rid of Devise, let’s see what it adds to warden:
- a few Strategies which you are probably familiar with (
- Controller filters and helpers
- All controllers and views. Which we don’t really need for an API.
- A default failure application responsible for redirection
Doesn’t sound too bad, doesn’t it? Let’s get started!
We’ll start with a really simple rails-api application that display the current time:
$ rails g controller Welcome index
Let’s also add the
bcrypt (to use
has_secure_password) gems to our application.
# Gemfile gem 'warden' gem 'bcrypt'
Note: the code source is available at https://github.com/ouranos/rails-api-warden
In my case, the app didn’t have a database and was authenticating against an external service.
For this example, we’ll keep it simple and use a User model.
Our model will have the following fields:
- an authentication token that can be passed as a parameter
- an encrypted password, that can be used to authenticate via HTTP Basic auth
Add Warden to the Rack Middleware stack
As explained in the setup page, we need to add Warden to our application middleware stack.
Let’s have a look at our current middleware stack:
$ rake middleware use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000052979d0> use Rack::Runtime use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag run RailsApiWarden::Application.routes
That’s a big list (and it’s even longer when using Rails)! Where should we put Warden?
According to the setup page, “Warden must be downstream of some kind of session middleware.”
Well, Rails API doesn’t enable any session management by default so let’s put it after the
Let’s check that Warden is in the stack:
$ rake middleware use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000037c80f0> use Rack::Runtime use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::ParamsParser use Warden::Manager use Rack::Head use Rack::ConditionalGet use Rack::ETag run RailsApiWarden::Application.routes
Now the documentation says “It must have a failure application declared, and you should declare which strategies to use by default.”
We’ll worry about the failure application later, let’s first add a strategy.
Strategy – Authentication Token
We’ll define a strategy to authenticate users via an
authentication_token passed in the params.
As explained in the Strategy page a
Strategy is a class that inherits from
Warden::Strategies::Base and implements an
#authenticate! tries to find an user matching the
authentication_token parameters and calls a method according to the result.
We’ve also added a
#valid? is optional and acts as a guard method for the strategy. If it returns
false, the strategy will not be executed. In our case, we skip the strategy if no
authentication_token has been provided. This can be useful when combining multiple strategies as we’ll see later.
You can see that the
authenticate! method calls
success!. These are public helper methods provided by Warden. Here are the most commonly used ones:
#success!Whenever you want to provide a user object as “authenticated”. This will halt the strategy, and set the user in the appropriate scope. It is the “login” method.
#failCauses the strategy to fail, but not halt. The strategies will cascade after this failure and warden will check the next strategy. The last strategy to fail will have it’s message displayed.
#fail!Causes the strategy to fail. Halts the strategies so that this is the last strategy checked
Now that our strategy is implemented, we need to add it to Warden:
Let’s add the familiar Devise helper methods (
signed_in?) to our controller as well as a
before_filter to authenticate our users:
And it works!
Adding a Failure Application
Now, you’ll realise that if you pass a wrong token you’ll get a pretty bad looking error page:
Remember that we didn’t declare a failure application? Let’s do this now!
Devise has a pretty intimidating one.
The failure application needs to be Rack application, so we can use ActionController::Metal to build a simple one and tell Warden about it:
Bonus round – Adding an extra strategy: HTTP Basic Authentication
Let’s a new strategy to our application. We want users to be able to authenticate via Basic Auth using their username/password combination.
Some useful links: