Easy Rails API Authentication Using restful-authentication
You’ve got a Rails application and you’d like to add an API. This tutorial assumes you are using restful-authentication.
Creating the API key
You’ll need to add a new column, api_key, to your users table. This field will store the unique API key used for authentication.
./script/generate migration AddApiKeyToUsers
class AddApiKeyToUsers < ActiveRecord::Migration def self.up add_column :users, :api_key, :string, :limit => 40, :default => "" end def self.down remove_column :users, :api_key end end
The API key is a simple SHA hash based on the current time and a random number — we need this to be unique. We’re not going to enable the API for a user by default, though you could easily do this using before_create. Instead, we’ll let the user enable or disable the API using the API Keys Controller, which we’ll create next.
Make the following changes to your app/models/user.rb file:
class User < ActiveRecord::Base def enable_api! self.generate_api_key! end def disable_api! self.update_attribute(:api_key, "") end def api_is_enabled? !self.api_key.empty? end protected def secure_digest(*args) Digest::SHA1.hexdigest(args.flatten.join('--')) end def generate_api_key! self.update_attribute(:api_key, secure_digest(Time.now, (1..10).map{ rand.to_s })) end end
Now, let’s create the API Keys Controller, which we’ll use to allow the user to create, re-generate, and delete their API Key. This effectively allows the user to enable and disable API access to their account.
./script/generate controller APIKeys
class APIKeysController < ApplicationController before_filter :login_from_cookie before_filter :login_required # Create or re-generate the API key def create current_user.enable_api! respond_to do |format| format.html { redirect_to edit_user_path(current_user) } end end # Delete the API key def destroy current_user.disable_api! respond_to do |format| format.html { redirect_to edit_user_path(current_user) } end end end
Add the resource to config/routes.rb
map.resource :api_key
You’ll need to allow the user to enable and disable the API, as well as re-generate their API key if they feel it’s been compromised. Add something like this to app/views/users/edit.html.erb:
<% if @user.api_is_enabled? %> <p> Your API Key: (<%= link_to "re-generate", api_key_path, :method => :post %> | <%= link_to "disable", api_key_path, :method => :delete %>) </p> <p> <strong><%= @user.api_key %></strong> </p> <% else %> <p> You'll need a unique key to make calls to the API. Remember to keep this key a secret as it can be used to access your account. </p> <p> <%= link_to("Get a key", api_key_path, :method => :post) %> </p> <% end %>
Authenticating the API Key
Add the following to lib/authenticated_system.rb:
def login_from_api_key self.current_user = User.find_by_api_key(params[:api_key]) unless params[:api_key].empty? end
Modify lib/authenticated_system.rb to add login_from_api_key as follows:
def current_user @current_user ||= (login_from_session || login_from_api_key || login_from_basic_auth || login_from_cookie) unless @current_user == false end
Optionally, if you’d also like to support access to the API via HTTP Basic Authentication using the API key in addition to a user’s login and password, you can make the following change to app/models/user.rb:
def self.authenticate(login, password) return nil if login.blank? || password.blank? if password.downcase == "x" # This is an API request u = find_by_api_key(login) else u = find_by_login(login.downcase) u && u.authenticated?(password) ? u : nil end end
When prompted to log in using HTTP Basic Authentication, use the API Key as the login and ‘X’ as the password.
Testing it Out
Assuming you have a RESTful resource called Items, you should be able to use curl as so:
curl http://www.yoursite.com/items/1.xml?api_key=356a192b
7 Comments to Easy Rails API Authentication Using restful-authentication
Leave a Reply
About Justin
Search
Recent Posts
- Not Having a Plan B Makes Plan A More Successful
- Easy Rails API Authentication Using restful-authentication
- God Init.d Script for CentOS
- Private, Authenticated RSS Feeds in Rails
- A Private Web Beta in Seconds with Prefinery
- Backup Your WordPress Blog to Amazon S3 using Ruby
- How to Set an Expires Header in Apache
- How to Configure Apache to Gzip Your Components



Very cool. I’ve been working on a similar way to authenticate users to edit stuff they’ve posted to my app, without having to create an account. Turns out you can override the default REST routes to require some kind of token or key to authorize the action:
map.resources :things do |thing|
map.edit ‘/things/:id/edit/:verification_code’, :controller => “posts”, :action => “edit”
map.confirm ‘/things/:id/confirm/:verification_code’, :controller => “things”, :action => “confirm”
end
Works fine in dev, but haven’t really tested it yet.
use the above code ( API Authentication Using restful-authentication) but i am facing an 500 error and i have checked my development log it shows the errors like /simpletwitterapp/app/helpers/api_keys_helper.rb to define ApiKeysHelper is there any thing has to define in it
Using:
./script/generate controller APIKeys
will give you some trouble because of the rails namings conventions
Use:
./script/generate controller ApiKeys
and you won’t have any problem like the one with the helper.
Awesome post. Very helpful. We’re rolling out some code at CrowdVine with this. Having it laid out meant it was easy to squeeze this feature in between other work. Thank you!
Thanx for sharing the code, it was pretty useful. But there is a security issue with the login_from_api_key method. The following finder finds a user, but it’s supposed to find none: User.find_by_api_key(”)
So any user can authenticate itself by leaving the api_key parameter value blank. This is how i do it:
def login_from_api_key
self.current_user = User.valid.find_by_api_key(params[:api_key]) unless params[:api_key].blank?
end
Thanks for tutorial, I implemented correctly and working so far but
I can access xmls without api key parameter passed.
I.e
/tasks.xml returning all tasks by XML format (need to asks for api key?)
/tasks.xml?api_key=randomkeyhere returning tasks by XML format normally
How can i restrict respond to xml blocks with only API key.
Regards.
Really helpful, thanks!