Tutorial: Authenticating a Rails API, Part 2

Posted by Helen Hood on Apr 25, 2016 2:21:52 PM

In our last post, we reviewed signing up users via a POST /users endpoint in our API.  In this episode, we'll review how we authenticate users using an authentication token passed in the authorization header.

Note: This tutorial is designed for someone who has a beginning to intermediate knowledge of Ruby on Rails.

Overview

We use Warden to authenticate our endpoints.  Warden lets you define custom strategies for restricting access to routes in your application. This is especially useful if you have different authentication rules for different endpoints.

In this post, we'll create a GET /widgets endpoint that can only be accessed by our users.  We'll require users to pass up their authentication token in an authorization header like so:

(In case you were wondering, the Token token= convention comes from RFC-2617, which suggests passing both the auth scheme -- “Token” -- and the auth param -- token=<my_auth_token> as shown above.)

There are three components we'll need to implement:

(1) A constraint to wrap our routes:

(2) The Warden strategy that the constraint uses to enforce authentication.

(3) A method on our User model that will find a user given an authentication token.  Our strategy will use this method to validate that a user with a given auth token exists.

Tools We'll Use

Let's get started.


Step 1: Write a test

By the time we get to implementing an authentication system, we usually have at least one endpoint that requires authentication. So let's start with a GET /widgets endpoint that we want to restrict access to. (Here’s the commit.)

Now let's add a test in our widgets_requests_spec.rb that ensures that a 401 status is returned if an unauthenticated user tries to access our endpoint:

This test should fail.

Step 2: Add a constraint

Rails' constraints allow us to define rules for restricting access to particular routes in your app.  A constraint class must simply implement a method called matches? that returns true if access is allowed and false otherwise.

Let's create a new constraint called AuthenticatedConstraint and use it to wrap our GET /widgets route.  Our constraint's matches? method will rely on the Warden strategy we'll implement next, which we'll call TokenAuthenticationStrategy to reflect the fact that it relies on an authentication token:

What's going on here?  First, our constraint is grabbing the information that the Warden middleware inserts into the request's environment.  If that is present, it then uses Warden to call the authenticate! method that we'll define on our TokenAuthenticationStrategy.  This is a convention of Warden strategies: they must implement an authenticate! method.

Then we're telling Warden about our strategy by calling Warden::Strategies.add (which we could really do anywhere, but here seems as good a place as any).  This stores our TokenAuthenticationStrategy in a hash of strategies that Warden keeps track of.

Step 3: Add our strategy

Now let's add our Warden strategy:

Let's walk through this.  Our class inherits from Warden::Strategies::Base, which gives us a few things:

  • A success! method that allows the request to proceed and sets the user in the request environment as request.env['warden'].user.
  • A fail! method that halts the request and calls Warden's failure app. The failure app tells Warden what to do if a request fails.  If we're using Monban to authenticate users using email and password, as we are, it sets up a default failure app for us that will return a 401 response.  If not, you'll have to set this yourself in application.rb:

Before calling authenticate!, Warden will first call our valid? method. If it returns false, Warden won't attempt to authenticate.

Our authenticate! method pulls the token out of the request headers, looks for a user with that (unexpired) token, and calls Warden's success! or fail! methods depending on whether it finds such a user.

There's one other thing here that's very important for authenticating API requests (as opposed to web requests).  Warden's store? method determines whether a user should remain logged in across requests. If not overridden, this method returns `true`, which will allow user a user to pass a valid auth token once and then remain authenticated across subsequent requests.  This is :no_good:.

Let's run our tests.  They should be passing!

Step 4: Extract a `User#for_authentication` method

We could leave as is and be done.  However, I like to extract the logic in TokenAuthenticationStrategy#user to a class method on the User model for easier testing. (commit)

See the demo app repo for the new method's tests.

That's it!

Tune in next time for our third part in this series, in which we'll add a POST /authentications endpoint to regenerate authentication tokens when a user's token expires.

Topics: Rails, API, json_spec, Warden

MORE ON DEVELOPMENT

About Intrepid

Intrepid is an end-to-end mobile design and development company with offices in Cambridge, MA and NYC. We help companies, from startup to enterprise, boldly navigate their mobile future. We provide our clients expertise in development, design, strategy, and technology integrations. At Intrepid, we are creating the best mobile products at the intersection of humanity & technology. Find out more at intrepid.io

Subscribe to Email Updates