A couple weeks ago we finally released Devise 1.1 which is fully-compatible with Rails 3! Not only that, we’ve been working with Rails 3 since the first betas and several features were added along the way! Let’s take a look at those, some architectural changes and see how Devise 1.1 and Rails 3 will change how you handle authentication.

Pretty URLs with Metal

A common complaint in Devise 1.0 (for Rails 2.3) was, in order to know which message to show to the user when sign in failed, we had to pass a parameter in the URL as in /users/sign_in?unauthenticated=true while one would expect us to simply use flash messages. This happened because the redirection was done not from inside a controller, but a Rack application set up in Warden (a Rack authentication framework Devise relies on) and we could not access flash messages from it.

However, since Rails 3 moved several responsibilities to the Rack layer, including flash messages, we can easily access flash messages from any Rack application, allowing us to remove the parameter from the URL! Even more, Rails 3 provides small, fast, bare bone controllers through ActionController::Metal, which we used in Devise to clean and speed up the code considerably.

Locking goodness

The lockable module in Devise also went through a major overhaul. Previously, it already supported :unlock_strategy as option, allowing you to specify if the user could be automatically unlocked after a time period, through an e-mail token or both. Now, it also supports :none as option, meaning that all unlocking should be done manually.

Even more, there is a new option called :lock_strategy, that allows you to specify whether the lock happens only manually or after an amount of invalid sign in attempts.

HTTP Authentication on by default

In Devise 2.3, you may remember that we had a module called :http_authenticable along with :database_authenticatable and :token_authenticatable. While all three worked great, it was confusing that all HTTP authentication features were built on top of the database authentication and it was not possible to do HTTP authentication using a token unless we created a forth module called :http_token_authenticatable. We quickly noticed this could be improved by providing a better design and better abstract Devise authentication strategies.

And that is what happened in Devise 1.1. Now both database and token authentication work through HTTP with no extra work and the http authenticatable module was deprecated. Besides, if you are creating a new strategy on your own, you get both authentication through parameters (form) and HTTP with no extra work!

Routing customizations

We built Devise to be a full stack solution with customization in mind. In Devise 1.1, the customization abilities from Devise were taken to the next level. Now the devise_for method in routes accepts to extra options: :skip and :controllers. The first one allows you to skip the routes generation for a given controller/module in case you want to define them on your own, while the second allows you to change the router to point to a given controller in your application, like Users::ConfirmationsController instead of Devise’s internal controller.

Talking about Devise’s internal controller, Devise 1.1 namespaced all controllers classes, so now we have Devise::ConfirmationsController instead of ConfirmationsController.

Another limitation removed from Devise in this new version is related to URLs customizations. In prior versions, Devise used the URL to retrieve which scope is being accessed. That said, if you were accessing “/users/sign_in”, Devise had to inspect this URL and find the “/users” bit to specify the current scope is “users”. The same happened to “/admin/sign_in”.

This had a huge impact in URL customization, because if you wanted to have an URL like “/some_prefix/users/sign_in”, you had to tell Devise you were appending a prefix. Things could get even uglier if you wanted to prepend dynamic prefixes like “/:locale”.

In Devise 1.1, we use the new contraints API and Rack capabilities from the new router to specify which scope to use. So, instead of inspecting the URL, Devise retrieves the user from the request’s env hash as request.env["devise.mapping"].

For all the routes generated by devise_for, Devise automatically sets this value in the env hash. However, if you are creating your own routes, you need to set it manually using the constraints API:

constraints lambda { |r| r.env["devise.mapping"] = Devise.mappings[:user] } do
  # Add a custom sign in route for user sign in
  get "/sign_in", :to => "devise/sessions"
end

Of course, since this is rather a common pattern, we encapsulated it in a nice API:

devise_scope :user do
  # Add a custom sign in route for user sign in
  get "/sign_in", :to => "devise/sessions"
end

You can simply give a block to devise_for as well and get the same result:

devise_for :users do
  # Add a custom sign in route for user sign in
  get "/sign_in", :to => "devise/sessions"
end

All the routes specified in the block have higher priority than the ones generated by devise_for.

Awesomeness pack

The last feature we want to discuss is also a routing customization, but we decided to leave it up for last because it shows all the potential coming with Rails 3 and Devise 1.1.

In Devise 1.1, we added the ability to require authentication for a given url in the router, besides the existing before filters in controllers. This allow us to easily require authentication for third party rack application without a need to hack into them. Kisko Labs posted an interesting case where you can use Devise to require authentication to a Resque application in very few lines of code:

authenticate :admin do
  mount Resque::Server.new, :at => "/resque"
end

Devise simply uses the constraints API discussed above, allowing the request to continue only if the user is already authenticated. Otherwise, it redirects the admin to the sign page managed by Devise inside your Rails application. Indeed, when you have Rack, Rails 3 and Devise 1.1 playing along, great things can be accomplished quite easily!

There are several other features, bug fixes and deprecations included in this release, we invite you to check the CHANGELOG and take a look at them!

And we are happy to say this is not all, there is much more to come in Devise 1.2, including OAuth2 support which is already added in the master branch. Enjoy!

Tags: , ,

This entry was posted on Thursday, August 19th, 2010 at 4:48 pm and is filed under English. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.

  • iteamon

    And finally my favourite part of merb-auth 'merged' into devise.
    +1,01 for authenticate :user do .. end in router! ;]

  • http://twitter.com/rgoytacaz Rodrigo Dellacqua

    Great job guys, being used this as my first stop for authentication. Kudos #plataforma

  • http://twitter.com/daglees Jamil Daglees

    Looking great! I'll be playing with this tonight.

  • Omar Garciapalencia

    I try to install devise on windows 7 and rails 3 running on ruby 1.9.2p0 and i get this: C:>gem install devise
    Building native extensions. This could take a while…
    ERROR: Error installing devise:
    ERROR: Failed to build gem native extension.

    c:/Ruby/bin/ruby.exe extconf.rb
    creating Makefile extension
    make is not recognized as an internal/external command
    Please, help
    Cheers