Devise internals and Warden

In my previous two blog posts I covered installing Devise and Devise modules. In this post I will be looking at how Devise works under the hood. For that, I’ll also need to look at Warden which is the gem that Devise is build on.

In this post I’ll investigate how some key features of Devise work. I’ll keep it short but I hope it will be enough to give you a better understanding of how Devise works.

How Devise obtains the current_user

current_user is defined dynamically in Devise’s source code. The method looks like this:

def current_#{mapping}
  @current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end

Which would be evaluated to:

def current_user
  @current_user ||= warden.authenticate(scope: :user)
end

As you can see the current_user method is using warden.authenticate(scope: :user) internally. This method will try to authenticate, passing in user as the scope. The reason that the scope is specified is because Devise makes use of multiple Warden scopes (eg: admin and user).

warden.authenticate also accepts a strategy argument. In this case, no strategy param is passed so Warden uses the default strategy.

class DatabaseAuthenticatable < Authenticatable
  def authenticate!
    resource  = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
    hashed = false

    if validate(resource){ hashed = true; resource.valid_password?(password) }
      remember_me(resource)
      resource.after_database_authentication
      success!(resource)
    end

    mapping.to.new.password = password if !hashed && Devise.paranoid
    fail(:not_found_in_database) unless resource
  end
end

This strategy is used to sign in the user. After doing some initial checks, the success!(resource) method is called, which is defined by Warden. This method will tell Warden to sign the user in.

Warden is then responsible for storing the user in a session variable, it does this by serializing the user to env["rack.session"]. If you’re interested in how that works you can browse Warden’s SessionSerializer class. It is essentially doing:

env["rack.session"]["key"] = user

Which is how you save a new session variable using Rack.

How Devise signs in a user

When a user tries to sign in, they are redirected to Devise::SessionsController. In the create action you can see the logic for signing in a user.

def create
  self.resource = warden.authenticate!(auth_options)
  set_flash_message!(:notice, :signed_in)
  sign_in(resource_name, resource)
  yield resource if block_given?
  respond_with resource, location: after_sign_in_path_for(resource)
end

The create action calls authenticate, which returns an authenticated resource. That resource is passed into warden.sign_in, which you can see here.

The warden.sign_in method will then authenticate the user using warden.authenticate!(auth_options) and it will redirect the browser back to the URL that was requested before Devise was triggered. For example, the browser hits users/1/accounts and is subsequently redirected to users/sign_in by Devise. After authenticating, Devise redirects the browser back to users/1/accounts.

That’s it!

I hope you’ve enjoyed learning a bit about how Devise works under the hood. I think you will find this knowledge helpful when you’re working with authentication in Rails.