After yesterday’s scattershot approach to trying to set up Warden for authentication in my Sinatra app (and trying to use the sinatra_warden gem), today I systematically went through the set up and configuration and shortly after lunch had everything up and running. Now I just need to decide how I want all of the access and redirects to work. I don’t know if anyone is ever going to use Warden with a Sinatra app that has an in-memory datastore, but I wanted to document the steps I took.

I also need to offer a huge tip of the hat to a gist by fellow Chicagoan Nicholas Young that helped get me going. I don’t know who you are, Nicholas, but if our paths ever cross I owe you a drink.

Getting Started

1) Add the warden gem to your gemfile. At some point you’re also going to need some form of authentication and I used ‘bcrypt-ruby’ so you could add that now as well.

gem 'warden' #*I'm using version 1.2.1*
gem 'bcrypt-ruby' #*I'm using 3.0.1*

2) Tell your Sinatra app to use warden

require 'warden'

3) Tell your Sinatra App to use Rack Session Cookies, which is where it will set the values for authenticated users.

use Rack::Session::Cookie

So at this point you have a Gemfile with an additional Gem or two and an App file that looks something like this:

require 'sinatra'
require 'warden'

class YourApp < Sinatra::Application
use Rack::Session::Cookie

get "/" do
erb 'index'.to_symm
end

get "/protected_pages" do
erb 'admin_only_page'.to_sym
end
end

use Rack::Session:Cookie can go anywhere inside the YourApp class, I just stuck it at the top for now.

Configuring Warden

Now it’s time to tell your app what Warden will do

1) Set up the Warden manager. This part…um, manages Warden, and this next section of code can also go anywhere inside the class of your main app (but I’d stick with top or bottom). I’ll do a line-by-line breakdown below, but what we’re doing is telling Warden what your authentication strategy (or strategies) is, what app will handle authentication failures, and what to serialize into and out of the session (that we just enabled in step 3 above). We also need to let Sinatra know specifically what method to use when authenticating, so we’ll set that too.

use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = YourApp
manager.serialize_into_session {|user| user.id}
manager.serialize_from_session {|id| Datastore.for(:user).find_by_id(id)}
end

Warden::Manager.before_failure do |env,opts|
env['REQUEST_METHOD'] = 'POST'
end

2) A Warden Strategy. In the second line of the code above we set the default authentication strategy to :password. You can configure as many strategies as you’d like beyond that, however, we’ll just stick with one for now. Below this block of code we’ll add in our strategy:

Warden::Strategies.add(:password) do
def valid?
params["email"] || params["password"]
end

def authenticate!
user = Datastore.for(:user).find_by_email(params["email"])
if user && user.authenticate(params["password"])
success!(user)
else
fail!("Could not log in")
end
end
end

The valid? method in this strategy is just a check that the user has at least typed in at least an email or a password and is trying to log in. IMPORTANT GOTCHA: the params being passed into Warden are indeed strings and not symbols, for example: "email" => "me@test.com", "password" => "n00b".

The authenticate!method is required by Warden and inside of it is some of the authentication that I set up myself. In the first line I ask my in-memory user datastore to find my user, and in the next lines I say that if it’s a valid user and the user authenticates its password, then it’s either success! or fail!, which are both methods required by Warden.

3) Failure, Serialize and the Method. In the lines following the default strategy setup (from the Warden Manager Setup gist above) we set up the remaining things the Manager needs to know. You can have any app handle failures, but in this case we’re just going to keep it in house, so failure_app is set to YourApp. I already mentioned the serialize and setting the 'post’ method for authentication, so let’s take a look at what our potential app file would look like now.

require 'sinatra'
require 'warden'

class YourApp < Sinatra::Application
use Rack::Session::Cookie

use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = YourApp
manager.serialize_into_session {|user| user.id}
manager.serialize_from_session {|id| Datastore.for(:user).find_by_id(id)}
end

Warden::Manager.before_failure do |env,opts|
env['REQUEST_METHOD'] = 'POST'
end

Warden::Strategies.add(:password) do
def valid?
params["email"] || params["password"]
end

def authenticate!
user = Datastore.for(:user).find_by_email(params["email"])
if user && user.authenticate(params["password"])
success!(user)
else
fail!("Could not log in")
end
end
end

get "/" do
erb 'index'.to_sym
end

get "/protected_pageges" do
erb 'admin_only_page'.to_sym
end
end

Setting Up Some Warden Methods & Routes

We’ve finished setting up the meat of the configuration so now we’ll just need to add in a way for users to login, logout and a way for us to work with the session information.

1) Using the Environment Variable & User Warden actually just stores itself as an environment variable at env['warden'], but instead of typing that every time I want to use it I added the following method:

def warden_handler
env['warden']
end

And once a user gets stored in session they are available at env['warden'].user but instead I just set up the following current_user method, which also makes current_user available in my views.

def current_user
warden_handler.user
end

Last but not least, I want to check authentication for the 'protected_pages’ route on my website, so I’ll create a check_authentication method:

def check_authentication
redirect '/login' unless warden_handler.authenticated?
end

Again, all I’m doing is asking the env['warden’] if it’s authenticated?

2) Login, Posting to a Session, Logout & Unauthenticated I’m not going to dive into buildling a login form or the pages for all of these, but here are the routes you’ll need to get started, and I’ll explain a few of the details below.

  get "/login" do
erb '/login'.to_sym
end

post "/session" do
warden_handler.authenticate!
if warden_handler.authenticated?
redirect "/users/#{warden_handler.user.id}"
else
redirect "/"
end
end

get "/logout" do
warden_handler.logout
redirect '/login'
end

post "/unauthenticated" do
redirect "/"
end

The login route is simply a route to the login form. Once a user enters their information and clicks “sign-in” I have the form post to “/session”, where it goes through and uses the authenticate! method that I set up in my default :password strategy. If it’s authenticated I direct a user to their own page, otherwise I’m currently redirecting them to the home page.

The logout route simple tells the warden_handler (i.e. env['warden’]) to logout, and redirects them to the login page.

The final route, post "/unauthenticated" is critical and if you don’t include it all failed authentications will end up on the “Sinatra doesn’t know this ditty” page. I just chose to redirect unauthenticated logins to the home page for now.

And a Loooong Blog Post Later, You’re Ready to Authenticate

So now the set up is done and hopefully you’ve already built out your views and whatever authentication code you need in your User class. All we need to do now is add the check_authentication to whatever route we want (or even in a Sinatra before filter):

get "/protected_page" do
check_authentication
erb 'admin_only_page'.to_sym
end

Before directing the user to the admin page it will ask the warden_handler if the user has been authenticated, and if they aren’t, the post "/unauthenticated" route will redirect them to the home page.

Wrap Up

There are LOTS of other things that I haven’t even touched on, like ways that you can set crumbs to direct users to the last page before login, but I just wanted to keep it simple for now and hopefully this little walkthrough has been of some use. I appreciate any feedback (either comments on the gists or you can find me on Twitter). Below is the complete YourApp code with the Warden specific stuff moved to the bottom (which is what I did in my own app). In my next post I’ll cover the Warden Test Helpers, which are really easy to use with Rspec.

require 'sinatra'
require 'warden'

class YourApp < Sinatra::Application
get "/" do
erb 'index'.to_sym
end

get "/protected_pagected_pages" do
check_authentication
erb 'admin_only_page'.to_sym
end

get "/login" do
erb '/login'.to_sym
end

post "/session" do
warden_handler.authenticate!
if warden_handler.authenticated?
redirect "/users/#{warden_handler.user.id}"
else
redirect "/"
end
end

get "/logout" do
warden_handler.logout
redirect '/login'
end

post "/unauthenticated" do
redirect "/"
end

# Warden configuration code
use Rack::Session::Cookie

use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = YourApp
manager.serialize_into_session {|user| user.id}
manager.serialize_from_session {|id| Datastore.for(:user).find_by_id(id)}
end

Warden::Manager.before_failure do |env,opts|
env['REQUEST_METHOD'] = 'POST'
end

Warden::Strategies.add(:password) do
def valid?
params["email"] || params["password"]
end

def authenticate!
user = Datastore.for(:user).find_by_email(params["email"])
if user && user.authenticate(params["password"])
success!(user)
else
fail!("Could not log in")
end
end
end

def warden_handler
env['warden']
end

def check_authentication
unless warden_handler.authenticated?
redirect '/login'
end
end

def current_user
warden_handler.user
end
end