Token Based Authentication in Rails

in Development

Token based authentication is when an API client uses a token identifier to make authenticated HTTP requests.

A lot of popular services offer token based authentication for connecting with their web API, like HipChat, Campfire, Backpack, Last.fm and many others. It’s not yet a standard, but there is an official draft that specifies the scheme.

tokens

Token based authentication offers many benefits over HTTP Basic and Digest Authentication:

  • More convenience, as we can easily expire or regenerate tokens without affecting the user’s account password.
  • Better security if compromised, since vulnerability is limited to API access and not the user’s master account.
  • The ability to have multiple tokens for each user, which they can use to grant access to different API clients.
  • Greater control for each token, so different access rules can be implemented.

Getting an API token usually means visiting a profile settings page on the service’s website and requesting an access key. Some might already have a key generated for us.

image

The Authorization header format for Token based authentication looks like so:

GET /episodes HTTP/1.1
Host: localhost:3000
Authorization: Token token=123123123

Rails Authentication

Rails offers the authenticate_or_request_with_http_token method, which automatically checks the Authorization request header for a token and passes it as an argument to the given block:

authenticate_or_request_with_http_token do |token, options|
  # authenticate user...
end

Inside that block is where we implement our authentication strategy. In the following example, we’ll authenticate our requests for the EpisodesController class.

class EpisodesController < ApplicationController
  before_action :authenticate

  def index episodes = Episode.all
    render json: episodes, status: 200
  end

  protected
  def authenticate
    authenticate_or_request_with_http_token do |token, options|
      User.find_by(auth_token: token)
    end
  end
end

Using a before_action, we call the authenticate_or_request_with_http_token method. We only care about the first argument, which is the token we’ll use to look up the user.

It is very important that the auth_token is unique across all users. In our User model, we use a before_create callback to generate the token.

require 'securerandom'
class User < ActiveRecord::Base
  before_create :set_auth_token

  private
  def set_auth_token
    return if auth_token.present?
    self.auth_token = generate_auth_token
  end

  def generate_auth_token
    SecureRandom.uuid.gsub(/\-/,'')
  end
end

The token generation process is delegated to SecureRandom.uuid. This method is part of Ruby’s standard library, and returns a Universally Unique Identifier (RFC 4122) that’s guaranteed to be unique across a global namespace (the gsub call removes dashes so the token is URL friendly). And if we wanted to take it a step further, we could also add a database level constraint, but we’ll skip that for now.

Using curl, we can test our token based authentication by passing a valid token in the Authorization header:

$ curl -IH "Authorization: Token token=a47a8e54b11c4de5a4a351734c80a14a" \
http://localhost:3000/episodes

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

Unauthorized

If the authentication fails and our block returns false, the request is halted and our application immediately responds with a 401 - Unauthorized status code.

$ curl -IH "Authorization: Token token=fake" http://localhost:3000/episodes.json
HTTP/1.1 401 Unauthorized
Content-Type: text/html; charset=utf-8
WWW-Authenticate: Token realm="Application"

According to the HTTP spec, a 401 - Unauthorized response must include a WWW-Authenticate header with a challenge applicable to the requested resource. The authenticate_or_request_with_http_token automatically includes that header for us:

WWW-Authenticate: Token realm="Application"

The Token part means that the given resource uses token authentication. The resource under that URI is currently part of the “Application” realm. The realm value allows protected resources to be partitioned into different sets of protection spaces, each with its own access policies.

The default realm value used by Rails is “Application”. To change it to a more descriptive value, we can pass the new name as an argument to the authenticate_or_request_with_http_token method.

authenticate_or_request_with_http_token('Premium') do |token, options|
  User.find_by(auth_token: token)
end

Limitations

One limitation we might come across when using authenticate_or_request_with_http_token is the fact that this method doesn’t allow for much customization. For example, it always responds with the Content-Type set to HTML regardless of the mime type requested by the API client. There’s also no easy way to add a custom error message to the response body if we wanted to.

For more flexibility, we can use the authenticate_with_http_token method and manually build the response ourselves:

class EpisodesController < ApplicationController
  before_action :authenticate

  def index
    episodes = Episode.all
    render json: episodes, status: 200
  end

  protected
  def authenticate
    authenticate_token || render_unauthorized
  end

  def authenticate_token
    authenticate_with_http_token do |token, options|
      User.find_by(auth_token: token)
    end
  end

  def render_unauthorized
    self.headers['WWW-Authenticate'] = 'Token realm="Application"'
    render json: 'Bad credentials', status: 401
  end
end

And that gives us a proper JSON response:

$ curl -IH "Authorization: Token token=fake" http://localhost:3000/episodes/1.json
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

For more information about token based authentication, the draft is available at http://tools.ietf.org/html/draft-hammer-http-token-auth-01. The code examples for this blog post are available on the BananaPodcast project on GitHub.

We cover this and other API topics in our Surviving APIs with Rails course on Code School. Even if you are not a Code School subscriber, you can still checkout the first level for free!

Have you ever implemented a token based authentication on your Rails API ? We’d love to hear your opinion in our comments!

– Carlos Souza (@caike)

(photo source: http://www.flickr.com/photos/kolix/2539213620)

UPDATE – 2014-11-01: Updated the post to use SecureRandom.uuid

Code School

Code School teaches web technologies in the comfort of your browser with video lessons, coding challenges, and screencasts. We strive to help you learn by doing.

Visit codeschool.com

About the Author

Carlos Souza

Developer, Instructor and Metal aficionado.

Might We Suggest