Wiring up Warden & Sinatra
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.
2) Tell your Sinatra app to use warden
3) Tell your Sinatra App to use Rack Session Cookies, which is where it will set the values for authenticated users.
So at this point you have a Gemfile with an additional Gem or two and an App file that looks something like this:
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.
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:
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.
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:
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.
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:
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.
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):
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.