HTTP Basic Authentication with Devise

by ewout

Devise is becoming a popular gem for adding modular authentication to a rails application. It builds on top of warden, which provides a pluggable architecture for multiple authentication strategies at the rack level.

Since the cool kids are using it, we did not want to be left out and ported our main application from restful_authentication. Aside from being cool, the switch will make porting to rails 3 easier, the latest devise is compatible.

The application uses basic http authentication for private RSS feeds and ical subscriptions. This is pretty common at the service level of an application, machines do not like login forms. Devise works with basic authentication out of the box, but it will only work when the authentication headers are already present in the request. When they are not, devise will return a 302 redirect to the login form and the RSS reader gives up.

The solution is to create a new devise strategy  in config/initializers/devise.rb

class HttpAuthenticatableNonHtml < Devise::Strategies::HttpAuthenticatable
  def valid?
    not request_format.html? or super
  end
  def http_authentication
    super or ''
  end
end
Warden::Strategies.add(:http_auth_non_html, HttpAuthenticatableNonHtml)

Warden needs to be instructed to use the strategy, inside the Devise.config block.

config.warden do |manager|
  manager.default_strategies.unshift :http_auth_non_html
end

The strategy will return a 401 with authentication realm when accessing a protected resource that is not html.

How warden and devise work in rails

Figuring out this solution required diving into the warden an devise code, which is quite intimidating at first. I created a diagram that hopefully makes it easier to understand the basic working of the authentication stack.

  1. The HTTP request enters the rack stack.
  2. Warden gets the request and forwards it in the rack stack, adding an environment variable “warden” that points to an authentication proxy.
  3. The request gets dispatched to the rails controller, which may call authenticate_user! from a filter. This is an alias for request.env['warden'].authenticate!(:scope => :user).
  4. The warden proxy picks an authentication strategy. Any strategy for which valid? returns true is tried.
  5. When authentication succeeds, a user object is returned to the controller. When it fails, the symbol :warden is thrown down the stack, and caught by the warden rack application. The latter will return a response, which is a redirect to the login page by default. This can be overridden by calling warden.custom_response!.
Fork me on GitHub