Authentication with Devise and Warden, part 1: Up and Running
In this post, I describe the authentication system I’ve chosen for the new Mealstrom: what it is, why I chose it, and how to get it up and running, plus the small issues I encountered. In subsequent posts, I’ll describe writing custom authentication strategies, which will lead to posts on Facebook Connect and on Twitter.
Last weekend my husband and I had our own all-night hackathon; his goal was site design, and my goal was to implement authentication — allowing users to log in and sign –in a way useful both for Mealstrom and for the yoga site. I wrote my own auth system for Mealstrom 1 and very much did not want to do so again. It was a very educational experience, but one of the overriding lessons was how difficult and distracting writing a good authentication system can be.
I did some investigating and my coworkers provided me with a number of recommendations of Rails authentication plugins. After researching what was available, I chose to go with the two-gem system of Warden (a Rack authentication framework) and Devise (a Rails framework on top of Warden).
Making the Choice
I chose Devise and Warden because they are (in no particular order):
- Flexible — they allows multiple, modular authentication mechanisms, support multiple resources (e.g. admin, user, etc.) simultaneously, and provide hooks to run your own code.
- Actively maintained — in fact, they’re pretty new.
- Simple to implement — after only a few steps (see below), you’re up and running with the provided modules and controllers.
- Comprehensive — they handle password resets, account confirmation emails, and so on. (It’s hard to overstate how cool this is.)
- Non-intrusive — Devise provides several controllers in its plugin, but overall they have a light footprint and don’t generate too much code.
I read up on other solutions — Authlogic and restful-authentication in particular — but what I read accorded with my coworkers’ concerns about the former being too inflexible and the latter being too happy with the code generation.
Getting It Running
Getting Devise and Warden up and running is pretty simple; I started at 11 PM,and I was done well before dawn. There are seven steps for the basic installation, each of which is straightforward. Most involve nothing more than running a command or inserting a line or two into your existing code. (For this, as well as the great gem itself, I’m deeply indebted to this post by Plataforma, the creators of Devise.)
- Install the gem:
sudo gem sources -a http://gemcutter.org/ # if you haven't already sudo gem install devise # this will also install warden
- Add them to your Rails project:
config.gem :devise, :version => "0.7.2" config.gem :warden, :version => "0.6.5"
- Run the installation script for devise, which adds the initializer to your project. If you want to customize the views for login, which you probably do, also run the view generator.
script/generate devise_install # adds devise.rb initializer script/generate devise_views # adds views/%w(sessions passwords confirmations devise_mailer)
- Choose which model(s) should represent users for authentication. This has three steps, column generation, an update to the model file, and routing:
- Create your columns with
script/generate devise user # or other model name
Of course, if you’re adding Devise to an existing project, you’ll need to create a temporary model (user2, anyone?) and massage the migration by hand. (See Bumps in the Road below.)
- Let your model know what’s going on. In this line from my user.rb file, I include all the parts of Devise except validations:
devise :all, :except => :validatable # we'll supply our own validations
- Finally, update routes.rb to tell Devise which resource(s) you want to use as your authenticatable model. Devise then generates appropriate routes for signing in, out, etc.
map.devise_for :user
- Create your columns with
- You’re ready to go — to require authentication in one of your controllers, just add
before_filter :authenticate!
before any method you want to require a logged-in user. (Update: Carlos from Plataforma reminded me that authenticate! only works if you have one resource — if you’re using Devise’s support for multiple authenticatable resources, use authenticate_#{resource_name}!, e.g. authenticate_user! or authenticate_admin!)
And that’s it! You’ve got authentication.
Bumps in the Road
Implementing Devise and Warden was, overall, a very smooth experience. Still, I did hit a few snags, which I’ll lay out here in the hope they’ll be useful to you.
- Undefined method `find_template’ for []:Array: I encountered this one due to some outdated instructions. Check out this issue on the Devise forums.
- Integrating with existing models: as mentioned above, the Devise generators are only set up to create new models, not to add Devise to existing models. You can get around this by creating a temporary model, but it’s awkward — besides creating a bunch of extra files, it forces you to move and massage code from these new model and migrations. Naturally, that increases the risk of mistakes (like I made, see the next bullet). Be careful with this.
- It’s not on their immediate roadmap to add support for existing models; I may take a stab at it myself if time allows. See this thread on the Devise forums.
- Routing or other basic problems: with the previous item in mind, I kinda forgot to add the line devise :all to user.rb. That caused all sorts of issues for a while. Make sure you don’t miss a step.
- Integrating sign-on in multiple places: it’s nice to let users log in from the signup page or directly from the home page. However, the Devise login screen as released depends on a number of variables derived by the session controller from the path. This is done to provide flexibility for multiple account types (admin, user, etc.), but makes it very hard to integrate it into other parts of your application.Fortunately for the 99% of us who only have one user type, you can simplify the form and the controller logic to get what you want. (This is the path recommended by the creators of Devise.) I created a partial called _login_form.html.erb containing a simplified version of the login form from sessions/new.html.erb:
<% form_for resource_name, resource, :url => session_path(resource_name) do |f| -%> <p><%= label_tag "#username_or_email", "Username or Email" %></p> <p><%= text_field_tag "username_or_email", params[:username_or_email] %></p> <p><%= f.label :password %></p> <p><%= f.password_field :password %></p> <p><%= f.submit "Sign in" %></p> <% end -%>
and a helper that provides the appropriate locals:
# hack to embed the devise login form on other pages def embed_login_form(resource_klass = User) render :partial => "shared/login_form", :locals => { :resource_name => resource_klass.to_s.downcase, :resource => resource_klass.new } end
You may have noticed that this isn’t exactly like the default Devise form — I wanted users to be able to log in with either their username or their email, and so wrote a new strategy and updated the default form. This conveniently segues into my next topic, creating custom strategies, coming soon.
Hello, pretty nice post about Devise. It’s a great explanation, I especially liked the “Bumps in the Road” part. I’d like to help with two simple tips:
* The :authenticate! filter is gonna work only when you have just one resource to authenticate. Whenever you’re dealing with many resources, you need to use the specific authenticate_*! methods:
before_filter :authenticate_user!
* In the last versions of Devise (not sure which version it was added) you have some options that let you use different keys to authenticate the user, not just the :email. The first is the :authentication_keys configuration, that is actually the params coming from the form that will be used for authentication. And its complement is the find_for_authentication class method you can override to find the user based on these conditions:
def self.find_for_authentication(conditions)
find(:first, :conditions => conditions)
end
Hope that might be useful =)
And thanks again for the great post. I Look forward to see the second part.
Carlos
Hey Carlos,
Thanks! I’ve really enjoyed working with Devise — thank you for the great gem. I’m glad to be able to contribute something useful back to the community.
You make two very good points. I’ve updated the post to include your note about authenticate! v. authenticate_*! — while I’m using only one resource, I want to cover as full a range of features with my blog posts (and multiple resources is a very cool feature).
As far as authentication_keys go, I saw that options when I set up Devise, but that feature didn’t seem to allow exactly what I wanted to do. If I read the code right, all the keys you specify are required to log in, so if you added username to email and password it would require all three.
Since each user on our sites will have a username, I want to offer the ability to log in using either a username or an email (entered interchangeably into the same field) along with the appropriate password. Since it’s probably an infrequent need, I wouldn’t expect that behavior to be supported out of the box; it seems like a great case for a custom strategy. (That’s actually the example I’m planning to use in the next entry to describe how to create your own strategies. Vacation allowing, I’m hoping to get the next entry out before the new year.)
Thanks again for your feedback!
Alex
Thanks! This is very helpful. I’ve decided to try working with Devise and this gave me some good background to get up and running. I would love to see a similar post about integrating Devise with Facebook Connect which will be my next step….
Thanks, Eric! I’ve had good experience with Devise, and hope it works out for you. Work and other projects have delayed following up on this post, but I do still hope and plan to write about using Devise with Facebook Connect and Twitter. I’ll comment again here when I post that.
In the meanwhile, you should check out Devise Facebook Connectable — it provides Facebook authentication and the author has been actively maintaining it and contributing code to Devise itself. Last I check it required you to use only Facebook Connect for accounts and authentication (no local accounts), so it doesn’t work for me, but maybe it can help you out.
Let me know what you find! I’m very interested in how others make Devise work for them.
[...] I’m now maintaining my technical blog posts on my personal blog site. Please jump to this entry there to see updates, live comment threads, and upcoming follow-up posts on building Devise strategies [...]
Hi Alex,
Thanks for the great post! It was really helpful when first trying to install Devise. Any chance of you doing a post on how to add a new strategy for logging in using a username or email?
So far, all I’ve found is that the strategy should be added to Warden (I thought it was supposed to be added to Devise), but I don’t know where/how to add and setup the code. (I’m still quite new to Rails.)
Anyway, it would be great if you could do a follow-up post on that, but I’m not sure how busy you are or what else you’ve got planned.
Either way, thanks again for the helpful post!
Hey Michael,
I’m about halfway through that post. I’ll let you know once it’s done — I’ve been away from Devise for a while but am finally getting back to authentication and the blog.
Thanks for your feedback!
Alex
Please don’t promise a part 2 if it’s not coming. You wrote a great post and I was really interested in the custom strategy part. Cheers.