Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
(Update: I've packaged this gist into a gem to make its use easier, see: https://github.com/gonzalo-bulnes/simple_token_authentication.) Add token authentication to your Rails API. Follows the José Valim recomendations and is fully compatible with Devise (tokens are created the first time users sign in). See https://gist.github.com/josevalim/fb7…
# app/controllers/application_controller.rb
# This application controller was extracted from a Rails-API application,
# the only line related to token authentication is the concern inclusion.
#
# The same line can be added to a regular Rails controller (which inherits from ActionController::Base).
#
# See https://github.com/rails-api/rails-api for details about Rails-API.
class ApplicationController < ActionController::API
include ActionController::MimeResponds
include ActionController::ImplicitRender
# Concerns
include TokenAuthentication
respond_to :html, :xml, :json
end
# Example:
#
# class ApplicationController < ActionController::Base
#
# # Concerns
# include TokenAuthentication
#
# respond_to :html, :xml, :json
# end
# app/models/concerns/token_authenticable.rb
module TokenAuthenticable
extend ActiveSupport::Concern
# Please see https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
# before editing this file, the discussion is very interesting.
included do
private :generate_authentication_token
before_save :ensure_authentication_token
end
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
end
module ClassMethods
# nop
end
end
# app/controllers/concerns/token_authentication.rb
module TokenAuthentication
extend ActiveSupport::Concern
# Please see https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
# before editing this file, the discussion is very interesting.
included do
private :authenticate_user_from_token!
# This is our new function that comes before Devise's one
before_filter :authenticate_user_from_token!
# This is Devise's authentication
before_filter :authenticate_user!
end
# For this example, we are simply using token authentication
# via parameters. However, anyone could use Rails's token
# authentication features to get the token from a header.
def authenticate_user_from_token!
# Set the authentication params if not already present
if user_token = params[:user_token].blank? && request.headers["X-User-Token"]
params[:user_token] = user_token
end
if user_email = params[:user_email].blank? && request.headers["X-User-Email"]
params[:user_email] = user_email
end
user_email = params[:user_email].presence
user = user_email && User.find_by(email: user_email)
# Notice how we use Devise.secure_compare to compare the token
# in the database with the token given in the params, mitigating
# timing attacks.
if user && Devise.secure_compare(user.authentication_token, params[:user_token])
# Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a
# sign in token, you can simply remove store: false.
sign_in user, store: false
end
end
module ClassMethods
# nop
end
end
# app/models/user.rb
class User < ActiveRecord::Base
# Concerns
include TokenAuthenticable
# Use any Devise module you want!
# Include some (non-default) devise modules. Others available are:
# :database_authenticatable, :registerable,
# :recoverable, :rememberable, :trackable, :validatable
# :confirmable, :lockable and :timeoutable
devise :omniauthable, :omniauth_providers => [:open_id]
end
# spec/models/user_spec.rb
require 'spec_helper'
describe User do
# attributes
# ...
it { expect(subject).to respond_to :authentication_token } # Token authentication
# methods
# ...
describe "#ensure_authentication_token" do # Token authentication
context "when the user has no authentication token" do
subject(:user_created_without_auth_token) { FactoryGirl.create(:user) }
it "creates one" do
expect(subject.authentication_token).not_to be_blank
end
end
end
end
# db/migrate/XXXXXXXXXXXXX_add_authentication_token_to_users.rb
class AddAuthenticationTokenToUsers < ActiveRecord::Migration
def change
add_column :users, :authentication_token, :string
add_index :users, :authentication_token, :unique => true
end
end
@OBCENEIKON

This comment has been minimized.

Copy link

OBCENEIKON commented Feb 5, 2015

Wouldn't lines 22 and 25 of token_authentication.rb need to be

    # Set the authentication params if not already present
    if user_token == params[:user_token].blank? && request.headers["X-User-Token"]
      params[:user_token] = user_token
    end
    if user_email == params[:user_email].blank? && request.headers["X-User-Email"]
      params[:user_email] = user_email
    end

instead?

@OBCENEIKON

This comment has been minimized.

Copy link

OBCENEIKON commented Feb 5, 2015

Also, it seems that leaving before_filter :authenticate_user! in token_authentication.rb makes authenticate_user! unusable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.