Nearly every web appliation requires user authentication. This introduction should help you save the evening I just spent trying to figure out how to use the Warden authentication layer in Padrino web applications.
What the Rack?
TL;DR -> If you want password-based authentication in your Padrino app – use this guide. 🙂
As you probably know Padrino (and many other Ruby frameworks) use Rack – which is the interface between the Ruby code and the actual web server that speaks HTTP. You can roughly compare it with CGI or WSGI (for Python). The advantage of Rack is that you can influence the way between the HTTP request, your application and the HTTP response. You can run the data through many different layers (called “middleware”) to mess with request and response. Warden is a Rack-based middleware that does exactly that. And it is designed to provide a mechanism for authentication in Ruby web applications. Warden is rather basic and acts transparently unless it is told to interfere. It is controlled through a Rack environment variable env[‘warden’].
Then sinatra-warden was conceived to make integration of Warden into Sinatra-based applications easier. Well, the software hasn’t been maintained for two years and its bug reports as just as old. But it seemed to serve as an example for the maker of padrino-warden. And since Padrino is based on Sinatra it helped make Warden easier to integrate into Padrino applications. So basically you just need t use the padrino-warden gem, configure how a successful login would work (depending on your database model or application logic) and create a template for a login page.
This may be trivia for experienced Ruby web developers. But it took me a while to figure out how things work together.
Installation
Add to the Gemfile of your project:
gem 'padrino-warden', :github => 'jondot/padrino-warden'
Why not just use the ‘padrino-warden’ gem that could get loaded from RubyGems? Well, the RubyGems version is 0.1.0 from 2010 – at least at the time I wrote this article. I already asked the maintainer to update the gem because his version on GitHub is actively maintained and currently (January 2014) version 0.19.0. I usually discourage using gems from GitHub because they are moving targets and next time you deploy your application you will probably get different code and different results.
Now in your shell from your project directory run…
bundle install
…and after a few seconds padrino-warden – and as a dependency warden – is installed.
Enabling Warden in your application
Next you have to register the Padrino::Warden module for your application. Just edit your app/app.rb file and near the other “register” lines add:
register Padrino::Warden
Configure how your model supports authentication
I assume that you already have a database model set up where you store usernames and passwords. In my application the model is called User and I use the email address as the identifier and a password. My model also has an “.authenticate” method that expects the email address and the password and either returns the user object (if login was successful) or nil/false (if login failed).
Once that is set up and worked (preferably with test code) you can add a file lib/authentication.rb (do not call it lib/warden.rb!) and make it look like this:
Warden::Strategies.add(:password) do def valid? params["email"] || params["password"] end def authenticate! if user = User.authenticate(params["email"], params["password"]) success!(user) else fail!("Could not log in") end end end Warden::Manager.serialize_into_session { |user| user.id } Warden::Manager.serialize_from_session { |id| User[id] }
Make sure that you customize the parts printed in bold letters for your purpose. The “valid?” method only tries the authentication if either of the fields is present. The “User. authenticate…” call should point to a method in your database model that returns the user if authentication was successful. Your method will likely search for the right user in the database based on the given email address and verify the password. And at the end change “user.id” into something that returns the ID (primary key) of that user record. Also turn “User[id]” into code that gets a certain user account from the database by its ID. “User[id]” works for Sequel. If you use ActiveRecord you will likely use something like “User.find(id)”.
Routes
Oh, by the way: padrino-warden adds a controller named “session” to your application. Just run “padrino rake routes” to see the extra routes you now have:
URL REQUEST PATH (:sessions, :unauthenticated) GET /unauthenticated (:sessions, :unauthenticated) PUT /unauthenticated (:sessions, :unauthenticated) POST /unauthenticated (:sessions, :unauthenticated) DELETE /unauthenticated (:sessions, :unauthenticated) OPTIONS /unauthenticated (:sessions, :unauthenticated) PATCH /unauthenticated (:sessions, :unauthenticated) LINK /unauthenticated (:sessions, :unauthenticated) UNLINK /unauthenticated (:sessions, :login) GET /sessions/login [1] (:sessions, :login) POST /sessions/login [2] (:sessions, :logout) GET /sessions/logout [3] (:sessions, :oauth, :callback) GET /sessions/oauth_callback
The most interesting routes are the ones I have marked [1], [2] and [3]. The process goes like this:
-
The user gets redirected to /sessions/login and the application will send them an HTML form with a field for the email address (or username) and the password. This form sends it data per POST method to /sessions/login. Yes, the URL is the same – but the method has been GET at first and is now POST. The controller code for /sessions/login is already there (from padrino-warden). You just need to create the template file in app/views/sessions/login.slim (or login.erb or whatever template language you use) and create the form there. In SLIM it looks as simple as this:
= form_tag url(:sessions_login), :method=>:post
= email_field_tag :email
= password_field_tag :password
= submit_tag -
The user has pressed the submit button and the form gets sent to the POST method action of /sessoins/login which is also build into padrino-warden. So there is nothing to do for you here. If the authentication was unsuccessful the login page is shown again. If the authentication worked the user gets redirected to ‘/’.
- If the user decides to log out they can be linked to /sessions/logout which invalidates the login session.
Restricting controllers and actions
Of course your users won’t login voluntarily. You will have to restrict certain controllers or actions. This is rather simple. Either add these lines to make the entire controller require authentication:
before do
login
end
Or just add the “logged_in? or login” expression at the beginning of the controller’s methods you want to secure.
Who is logged in?
padrino-warden provides a “user” (alias: “current_user”) variable for your templates that contains the actual user object according to your database model. So these lines (SLIM syntax) will show who is logged in or provide a login link:
- if logged_in? a href="#" Logged in as #{current_user.email} - else a href="#" Anonymous
Objects
By the way these are the objects that padrino-warden gives you:
- warden → Warden object
- authenticate / login → render the login form
- authenticated? / logged_in? → true if the user is logged in
- logout → invalidate session
- user / current_user → user object if a user is logged in
Authorization
Now I will have to start thinking about how to realize authorization… suggestions welcome. 🙂
What is the benefit of using Warden over the built-in Padrino authentication (http://www.padrinorb.com/api/Padrino/Admin/AccessControl/Base.html#allowed%3F-instance_method) ?
In my opinion the advantage is the versatility. The kind of web applications I create are hardly ever the typical CRUD type. So you can assume I have more expectations of what a user model provides. Padrino's admin is a nice idea but I have quickly dropped it again. Reasons:
I have discussed that with the Padrino administrators and it seems that the Admin component will be taken out of the core because it's not really ready for reality yet. Of course it will stay available as a plugin.
Another grief I have with the built-in authentication/authorization module is this:
You are putting restrictions on controllers in a central place. That's exactly the kind of centralized thinking that drove me away from Rails. I love specifying URL routes in the controllers instead of a central config/routes.rb file. Then why am I supposed to define access control in a central place?
At the end of the day it's a matter of taste and the requirements of your web applications. But I really don't see any good use for the Admin component in my applications – at least not yet.
I totally agree with you regarding the admin interface which comes with Padrino – it has no real practical use, especially when using associations.
However, like you, I am trying to add authentication to my Padrino app, and I’m not seeing a lot of functionality there either. When I first starting pursuing this Ruby thing, I was excited to hear about the simplicity of Rails. I was especially excited about the Devise gem. Then I stumbled into Sinatra and Padrino. After crawling down a few rabbit holes, I found a few people suggesting Warden. Now, after actually integrating it into my app, I’m surmising that it’s more trouble than it’s worth. Does it really do that much which can’t be done by manipulating the session object? You’ve already created an authentication method, why not just create more methods to logout a user by destroying the session and the other minor functionality I’m seeing Warden providing?
Carl,
you have a point there. Back in the days when I was using Pylons (a Python web framework with a similar concept as Padrino) there were several ways to do authentication. You could use additional modules. You could use WSGI middleware. Or you could roll your own. And in fact most developers just rolled their own layer of authentication just like you suggest.
Comparing the two solutions:
before do
login
end
before do
"get user from session"
"check if session has expired"
"redirect to login form unless session is okay"
end
(You could write helpers to do that in a lib/….rb place.)
There may be me more arguments but that's what just came up to my mind. Considering these aspects I would say that rolling your own is at least as good as using Padrino-Warden. I hoped to even that out by the cool authentication strategies for e.g. OpenID. But seeing that the recommended OpenID module for warden hasn't been touched in 4 years doesn't give me a good feeling.
I also just realized that my bug report about a serious outdated gemon rubygems.org hasn't even been replied to. That does not leave me with a good feeling about a security-related dependency in my software. I will likely throw Padrino warden out.
…Christoph