Creating an API for one project at work, one odbrhw tasks was to implement a token based authentication for some resources, but the client specifically requested not to have to handle cookies.
Also, it was requested for the user to still have to login with it’s own login and password, rather than with a permanent token, like a permanent API key.
The solution I implemented used the excellent authlogic capabilities with the single_access_token, although used slighlty differently from it’s original purpose.
Rather than keeping the single access token generated at user registration untouched, like a standard API key, I enforced it’s regeneration at both login and logout. Returned in the login response, that token then has to be provided by the client for every request that needs authentication, effectively playing the same role as a cookie.
With this solution, the client looses the ability to stay logged in by storing the credentials in the client’s machine, but as the project it’s been created for only required an API, there was no problem with that.
Implementing this solution simply puts a little big more work on the client to store and provide the token in the requests parameters, but I still found it an elegant solution to get around my problem.
The following code implements this solution in the Application and the User_Session controllers, showing the regeneration of the token in both login and logout actions with authlogic’s reset_single_access_token method.
app > controllers > user_sessions_controller
class UserSessionsController < ApplicationController
def create
@user_session = UserSession.new(params[:user_session])
respond_to do |format|
if @user_session.save
current_user.reset_single_access_token!
format.xml
else
format.xml {render :xml=>@user_session.errors, :status=>:unauthorized}
end
end
end
def destroy
if(@user_session = UserSession.find)
current_user.reset_single_access_token!
@user_session.destroy
respond_to do |format|
format.xml {render :xml=>{:status=>'200 ok'},:status=> :ok}
end
else
respond_to do |format|
format.xml {render :xml=>@user_session.errors, :status=> :not_found}
end
end
end
end
app > models > user
class User < ActiveRecord::Base acts_as_authentic end
app > views > users_sessions > create.xml.builder
xml.instruct! :xml, :version=>"1.0"
xml.user{
xml.user_id(current_user.id)
xml.user_credentials(current_user.single_access_token)
}
app > controllers > users_controller
class UsersController < ApplicationController before_filter :check def create end def index end def update end def show end end
db > migrate > create_users
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :username
t.string :crypted_password
t.string :password_salt
t.string :persistence_token
t.string :single_access_token, :null => false
t.timestamps
end
end
def self.down
drop_table :users
end
end
Hi,
I see that in your solution there is one tricks, when user first authenticated through client and then through Web, then they cannot works in parallel, because you reset single token, maybe better, reset it only for format xml.
But also you cannot logged in parallel through two clients. Otherwise maybe better use toke of session not user’s model.
Paul
Hi Paul,
I agree with you, this solution is no where near universal and has its problems as you pointed out.
However, it has been working fine for me with two clients (a web client and an XML API client) logged in at the same, as both use different authentication methods.
The XML API uses the single_access_token, while in my application, I use the excellent authlogic, which I think uses the persistence_token of the user model. As both are different in the DB, this works well.
Also, admitting that both solutions would use the same single_access_token, resetting it only for one format would lead to strange results, and an inconsistent behavior for the user.
Now, if you want to access a website both on a web desktop and lets say on a web mobile, for sure, there will be a problem with my solution, but my guess is this will not happen often.