Devise: flexible authentication solution for Rails

UPDATE: This post was an introduction to Devise and a couple of things changed since then. There is a more recent post which describes the same steps as below using generators and, for a more complete and always updated explanation, please check the README.

In Rails Summit Latin America 2009, we showed Devise in a lightning talk and today we are officially releasing it! Before we show you some code, we are going to explain what we want to achieve with Devise, starting with the most used authentication solution nowadays: Clearance and Authlogic.

Clearance

Clearance is a full stack authentication solution, implementing all Model, View and Controller layers using Rails Engines. It deals with account confirmation and password recovery. You just need to plug and play! However, you are required to use the model User and it does not allow you have add and/or customize different roles.

Authlogic

When comes to the Model, Authlogic is definitely the most complete solution out there. It handles several cryptography providers and many other goodies which are completely configurable. However, it’s not a full stack solution (it does not say how users should confirm their account or recover their password) and it has a little bit of controversy since it handles the session in a model. So here is the question, where the session could be handled then?

Here comes Warden!

Warden is a general rack authentication framework, developed by Daniel Neighman, which handles the session in a rack middleware. The main benefit from it is that you can share your authentication strategies with several apps, so if you are using Sinatra, Rails and some others middlewares at the same time, they all rely on the same rules!

Devise: strategies for authentication

After we fell in love with Warden and used it in some projects, we decided to create a full stack solution as Clearance, but flexible as Authlogic, on top of Warden. The solution is Devise, a Rails Engine which handles multiple roles, each one of them with different strategies. Devise currently comes with 5 strategies:

  • Authenticatable: responsible for encrypting password and validating authenticity of a user while signing in;
  • Confirmable: responsible for verifying whether an account is already confirmed to sign in, and to send emails with confirmation instructions;
  • Recoverable: takes care of reseting the user password and send reset instructions;
  • Rememberable: generates and clears a token to remember the user from a saved cookie;
  • Validatable: creates all needed validations for email and password. It’s totally optional, so you’re able to to customize validations by yourself.

The nice thing is: imagine that you are building an app which needs to handle invitations. You just need to create a Invitable strategy on Devise and never implement it again!

Show me the code!

In the README, you will find all the information you need to start using Devise in your projects, so here we are going to cover the main aspects of it. Let’s suppose you are creating an user model, which needs to be authenticated and recover his password. The first step is to create the columns using Devise migration helpers:

create_table :users do |t|
  # creates email, encrypted_password and password_salt
  t.authenticatable

  # creates reset_password_token
  t.recoverable
end

Then you need to declare inside your model which strategies you want to use:

class User 

And create the routes:

ActionController::Routing::Routes.draw do |map|
  # Check for configuration params on README
  map.devise_for :users
end

The route will access your model and create only the routes for the strategies declared. That ensures that your user won't access the confirmations controller inside Devise. Devise also adds a couple of helpers and filters to be used inside your controllers:

  # Inside your protected controller
  before_filter :authenticate_user!

  # Inside your controllers and views
  user_signed_in?
  current_user
  user_session

user_session is a hash scoped only to the user. So if you have two roles, they will have different session hashes and their data won't conflict! This awesome feature come straights from Warden!

Devise also has I18n support and since it's an engine, you can customize your views just by placing a copy of it in your application. A small application build as example is also available on Github!

What's more to come?

We are planning to add several other strategies to Devise, including brute force protection, session timeouts and also other features, as generators. You can spy our TODO list whenever you want.

Our many thanks to

Carlos Antônio which worked on Devise and made it ready for prime time! Jonas Nicklas, which introduced us to Warden and Daniel Neighman for building and maintaining it!

We also want to thank Thoughtbot guys, which wrote several decisions and tips they took while developing Clearance which helped us while building Devise.

Finally, thanks to Fábio Akita for giving us the chance to release it at Rails Summit and Gregg Pollack for releasing Devise on Ruby 5!

69 responses to “Devise: flexible authentication solution for Rails”

  1. Very interesting this post, thak you for your job, I will do some tests and make tutorials for brazilians…

    Thank you..

  2. Very interesting this post, thak you for your job, I will do some tests and make tutorials for brazilians…

    Thank you..

  3. Does it support OpenID?

  4. Does it support OpenID?

  5. José Valim says:

    Not yet, but due to how Warden and Devise were designed, add OpenID support should be easy!

  6. José Valim says:

    Not yet, but due to how Warden and Devise were designed, add OpenID support should be easy!

  7. Teddy says:

    I really like the way this is set up. I’ll try this out in my next two upcoming projects.

  8. Teddy says:

    I really like the way this is set up. I’ll try this out in my next two upcoming projects.

  9. Claudio Poli says:

    Cool stuff, thanks, will try it out in next project

  10. Claudio Poli says:

    Cool stuff, thanks, will try it out in next project

  11. Mike Wyatt says:

    I can see the ActionController::IntegrationTest helpers, but can’t for the life of me figure out how to sign in a user in a functional test. I’ve mucked around, but am not getting very far. any ideas?

  12. Mike Wyatt says:

    I can see the ActionController::IntegrationTest helpers, but can’t for the life of me figure out how to sign in a user in a functional test. I’ve mucked around, but am not getting very far. any ideas?

  13. José Valim says:

    @Mike, the way to make functional tests work is by setting a mock in @env[:warden] and stubbing the appropriated methods. Maybe we should add a helper that does all required stubs?

  14. José Valim says:

    @Mike, the way to make functional tests work is by setting a mock in @env[:warden] and stubbing the appropriated methods. Maybe we should add a helper that does all required stubs?

  15. […] Devise A full stack Rails authentication engine built on top of Rack-based Warden that uses a strategy provider pattern. Use of Rack allows authentication to be shared across different types of applications (Rails, Sinatra, etc) and strategy pattern allows new, customizable strategies (i.e. Invitable) to be created and shared. Very interesting… […]

  16. Jerod Santo says:

    @Jose yes, a test helper would be great or even a wiki page describing the required stubs might do. Looks like an interesting alternative, thanks!

  17. Jerod Santo says:

    @Jose yes, a test helper would be great or even a wiki page describing the required stubs might do. Looks like an interesting alternative, thanks!

  18. Has it been tested against Rails 3 yet? Authlogic and Clearance breaks in Rails 3. I think it will be prime time if it is compatible with Rails 3.

  19. Has it been tested against Rails 3 yet? Authlogic and Clearance breaks in Rails 3. I think it will be prime time if it is compatible with Rails 3.

  20. José Valim says:

    @Joshua, right now makes no sense to be compatible with Rails 3 while the API is still changing. We may start to look into it when a beta is released.

  21. José Valim says:

    @Joshua, right now makes no sense to be compatible with Rails 3 while the API is still changing. We may start to look into it when a beta is released.

  22. Mike Wyatt says:

    this is what I’ve come up with. it’s crude, uses only the :user scope in warden and uses Factory Girl, but it was the first thing that worked for me.

  23. Mike Wyatt says:

    this is what I’ve come up with. it’s crude, uses only the :user scope in warden and uses Factory Girl, but it was the first thing that worked for me.

  24. Mike Wyatt says:

    and a link would be helpful eh
    http://gist.github.com/219330

  25. Mike Wyatt says:

    and a link would be helpful eh
    http://gist.github.com/219330

  26. @Mike, pretty nice your method for mocking warden.. thanks for that.
    We are working on this helpers for functional testing, and it will be useful. Thanks.
    Carlos Antonio

  27. @Mike, pretty nice your method for mocking warden.. thanks for that.
    We are working on this helpers for functional testing, and it will be useful. Thanks.
    Carlos Antonio

  28. Bruce Perens says:

    I must be missing something. Is the API documentation online, or do I have to extract it myself? How should one create a new user? Sorry if that was all obvious and I just didn’t see where it was.

  29. Bruce Perens says:

    I must be missing something. Is the API documentation online, or do I have to extract it myself? How should one create a new user? Sorry if that was all obvious and I just didn’t see where it was.

  30. José Valim says:

    @Bruce, the main documentation until now is the README. We will find a place to release the rdoc documentation, but you can always use `gem server` on your machine.

    Devise does not say anything on how your user should be created (sign up). But that should be quick, just two views! 😉

  31. José Valim says:

    @Bruce, the main documentation until now is the README. We will find a place to release the rdoc documentation, but you can always use `gem server` on your machine.

    Devise does not say anything on how your user should be created (sign up). But that should be quick, just two views! 😉

  32. Ludovic says:

    Does it support OAuth ?

  33. Ludovic says:

    Does it support OAuth ?

  34. José Valim says:

    @Ludovic, not yet! But due to how Warden and Devise were designed, add OpenID and OAuth support should be easy!

  35. José Valim says:

    @Ludovic, not yet! But due to how Warden and Devise were designed, add OpenID and OAuth support should be easy!

  36. Jochen says:

    Hi,

    can I use couchdb with devise?

  37. Jochen says:

    Hi,

    can I use couchdb with devise?

  38. José Valim says:

    @Jochen, if your CouchDB ORM mimics ActiveRecord interface, it shouldn’t be hard at all because Devise just include one module called Devise::ActiveRecord in ActiveRecord::Base. All you need to do is a small patch where, before including this module, Devise checks if ActiveRecord::Base is really defined and then you should include Devise::ActiveRecord (we should probably rename it to Devise::Model) in your CouchDB ORM.

  39. José Valim says:

    @Jochen, if your CouchDB ORM mimics ActiveRecord interface, it shouldn’t be hard at all because Devise just include one module called Devise::ActiveRecord in ActiveRecord::Base. All you need to do is a small patch where, before including this module, Devise checks if ActiveRecord::Base is really defined and then you should include Devise::ActiveRecord (we should probably rename it to Devise::Model) in your CouchDB ORM.

  40. Jochen says:

    @josé, I am using Couchrest as CouchDB ORM. So I just have to include Devise::ActiveRecord in Couchrest?

  41. Jochen says:

    @josé, I am using Couchrest as CouchDB ORM. So I just have to include Devise::ActiveRecord in Couchrest?

  42. José Valim says:

    @Jochen, I just commited to Devise a patch that should, at least in theory, remove ActiveRecord dependency. Please install Devise as a plugin and all you have to do (if Couchrest mimics ActiveRecord API) is to include Devise::Models in Couchrest base (the one you inherit from).

    If everything works great, I will release Devise 0.3.1, otherwise we can work on adjustments until it works for you. 🙂

  43. José Valim says:

    @Jochen, I just commited to Devise a patch that should, at least in theory, remove ActiveRecord dependency. Please install Devise as a plugin and all you have to do (if Couchrest mimics ActiveRecord API) is to include Devise::Models in Couchrest base (the one you inherit from).

    If everything works great, I will release Devise 0.3.1, otherwise we can work on adjustments until it works for you. 🙂

  44. +1 for OpenID, especially getting the views and controllers setup for it as well, possibly with something like rpxnow.com or clickpass.com which a lot of sites are using now.

    Setting up a login screen which does both (example: http://buyersvote.com/user_session/new ) still takes a long time and lots of hacking with AuthLogic.

    Great work!
    Brian

  45. +1 for OpenID, especially getting the views and controllers setup for it as well, possibly with something like rpxnow.com or clickpass.com which a lot of sites are using now.

    Setting up a login screen which does both (example: http://buyersvote.com/user_session/new ) still takes a long time and lots of hacking with AuthLogic.

    Great work!
    Brian

  46. José Valim says:

    Hey @Brian, I’m also +1 on OpenID, but we are not going to implement it until we need for an app we are working on. That can take one week or one month, we never know!

    That’s why I say that anyone who wants to implement it first can count with my support! 🙂

  47. José Valim says:

    Hey @Brian, I’m also +1 on OpenID, but we are not going to implement it until we need for an app we are working on. That can take one week or one month, we never know!

    That’s why I say that anyone who wants to implement it first can count with my support! 🙂

  48. Mike Wyatt says:

    so I’m still trying to get authentication into functional tests. after realizing that what I had before would modify the controller for ALL the tests the need for a block was added. so now you must do `mock_warden!(:user => Factory(:user)) { post :create }`

    but what I’m having trouble with now is if you use `:user => nil`, it still passes the `authenticate_user!` filter, and I can’t figure out any way of preventing this

    the code is available at http://gist.github.com/219330, but I can’t stress enough that I have no idea what I’m doing

  49. Mike Wyatt says:

    so I’m still trying to get authentication into functional tests. after realizing that what I had before would modify the controller for ALL the tests the need for a block was added. so now you must do `mock_warden!(:user => Factory(:user)) { post :create }`

    but what I’m having trouble with now is if you use `:user => nil`, it still passes the `authenticate_user!` filter, and I can’t figure out any way of preventing this

    the code is available at http://gist.github.com/219330, but I can’t stress enough that I have no idea what I’m doing

  50. José Valim says:

    @Mike, it’s odd that changing the env in one controller will change for all tests. If that is the case, instead of working with a block, I would suggest to remove the warden environment in teardown.

    class ActionController::TestCase
      teardown :cleanup_warden_env
    
      def cleanup_warden_env
        @controller.request.env.delete('warden')
      end
    end

    And about the “:user => nil” I would suggest you to, while you stub, check if a value was given, if so, make the method call raise an error:

    if v
      warden.stubs(:authenticate!).with(:scope => k) do
        raise "Not authenticated user"
      end
    else
      warden.stubs(:authenticate!).with(:scope => k)
    end

    I’m not sure that syntax works for mocha, but something similar must be available. What do you think?