Devise Authentication Strategies

Duncan Robertson
Kyan Insights
Published in
4 min readOct 11, 2013

--

Despite the rather grandiose title, this bit of extendable functionality that the popular Authentication library Devise exposes is really rather simple in concept. It’s also proved to be extremely useful, even though coming across it’s existence required a bit of detective work.

Here’s the elevator pitch:

Conceptually an authentication strategy is where you put the logic for authenticating a request.

Background

Devise is built on top of a library called Warden. Warden is a low-level authentication library for Rack and works as a piece of middleware in the stack. Warden provides the basis for Strategies by implementing a Base strategy from which others must inherit.

Devise actually declares 2 strategies Authenticatable and DatabaseAuthenticatable of which you can choose :database_authenticatable. Authenticatable inherits from Wardens Base Strategy and DatabaseAuthenticatable inherits from Authenticatable.

Implementation

A Strategy is just a class that follows a few rules. Warden specifies that a Strategy must declare an authenticate! method and can optionally declare a valid? method.

Below I have created a simple madeup Strategy called Password. I have added the authenticate! method. It simply calls an authenticate class method on User and passes it some parameters. The results then decide which utility method gets called (more of which I’ll explain later).

module Devise 
module Strategies
class Password < Base
def authenticate!
u = User.authenticate(params['username'], params['password']) u.nil? ? fail!("Could not log in") : success!(u)
end
end
end
end

Now I’ll add the valid? method. valid? acts as a guard for the Strategy. It should return true or false depending on if the strategy is valid for the request. If it returns true, the Strategy will be run otherwise it is skipped. You can have many Strategies you see, and they cascade. So in the code below, it will only run the Strategy if the correct params are available. You can imagine a Strategy that only runs if certain request headers are available.

module Devise
module Strategies
class Password < Base
def valid?
params['username'] || params['password']
end
def authenticate!
u = User.authenticate(params['username'], params['password']) u.nil? ? fail!("Could not log in") : success!(u)
end
end
end
end

You can see the fail! and success! methods used in the authenticate! method. There are a bunch of these public utility methods Warden makes available. Devise also exposes some if your Strategy inherits from Authenticatable. I’ll run through the ones you’ll likely use more often. You can see the rest in the Warden source declaration.

#success!

This is called when you want to provide a resource as ‘authenticated’. This will halt the strategy, and set the resource in the appropriate scope. This is effectively the ‘login’ method.

#fail

Calling fail causes the strategy to fail, but not halt. The request will cascade to the next strategy (if available) passing along any failure message. The last strategy to fail will have it’s message displayed.

#fail!

This also causes the strategy to fail. It also Halts the strategies so that this is the last strategy checked.

#headers

Provides access to the headers hash for getting and setting custom headers.

Adding to Rails

Once you have your new Strategy, it’s time to add it to your application. I add mine as an initializer, so for this example you’d add it to:

/config/initializers/password_strategy.rb

I tend to prefix my initializers with numbers so that I know the order they will be loaded in. The important thing is that the:

/config/initializers/devise.rb

initializer is loaded before any custom strategies.

Next you need to tell devise to load the strategy, and to stick it on top of the pile so it is used first.

config.warden do |manager|
manager.strategies.add(:password, Devise::Strategies::Password)
manager.default_strategies(:scope => :user).unshift :password
end

Restart your app and you’re done. The new strategy will now be used.

Real World

This post came of the back of a real world example. Below is a version (with added pseudo code) of the strategy we needed to use for a client that was changing authentication solutions and needed it to be transparent to the user. We wanted this migration to be elegant and not cause headaches when the old one needed to be removed.

module Devise
module Strategies
class PasswordUpgradable < Authenticatable
def authenticate!
auth_params = authentication_hash
resource = mapping.to.find_for_authentication(auth_params)
return fail!(:not_found_in_database) unless resource if legacy?(resource) && legacy_authenticates?(resource, password)
resource.skip_confirmation!
resource.password = password
resource.password_confirmation = password
resource.the_old_password = nil
unless resource.save
fail!(resource.unauthenticated_message)
end
else
return pass
end
auth_params[:password] = password if validate(resource){ resource.valid_password?(password) }
success!(resource)
end
end
private def legacy?(resource)
resource.the_old_password.present?
end
def legacy_authenticates?(resource, password)
resource.the_old_password == encrypt(resource, password)
end
def encrypt(resource, password)
# some old encryption code
end
end
end
end

The beauty of doing this in a Strategy was any remnants of the old authentication system is now in the strategy and when we are happy everyone has been migrated, we just removed the Strategy and Devise will revert to it’s own. Simple.

Resources

Here’s some useful reading that helped me understand Authentication Strategies.

Originally published at kyan.com on October 11, 2013.

--

--