Authenticate like SSO with ActiveResource
Courtenay : July 18th, 2008
When you have multiple Rails applications that don’t share a common database and you want to share the user authentication information – or rather, use one app to provide authentication for another – there are a few options. Here’s how I solved it recently. This is the simplest way I could think of to get this working. I couldn’t find a plugin to do this, so here’s the result of my pdi.
Effectively what we’re doing is separating the user’s data - their profile info, if you like - from the credentials, and moving the latter to ActiveResource. This is something you should do in your own apps. Too frequently we stuff a bunch of data (like full name, phone number) into the user model, because it’s there. A more advanced version of this code might use the ‘profile’ as the resource name, updating the local profile with data from remote, and keeping User as a pure credential model.
Let’s assume we have App A which will act as the authenticator master. Our other application, App B, will still hold a User record, but we’ll override the authenticate method to use ActiveResource. We’ll also store some other fields like username and email, and will grab those each time the user logs in. That way, they can set an auth token in App A and they can login from cookies in app B (provided the cookie domain is shared).
class User < ActiveRecord::Base
class Auth < ActiveResource::Base
self.site = "http://app-a.com"
self.format = :json
self.element_name = 'user' # this is the name of the resource in your app
end
def self.authenticate(login, password)
Auth.user = login
Auth.password = password
# Authenticating against the app will actually 'prove' the login/pass details.
# We also want the user's details so we can cache them here.
authed = Auth.find :first, :params => { :login => login }
return false unless authed
# Now, pull the data from remote and store it locally.
user = User.find_or_initialize_by_login(login)
user.attributes = authed.attributes
user.save!
user.activate!
user
rescue ActiveResource::ClientError # 406 error -- bad username/password.
false
end
Interestingly enough, find first actually runs the ‘index’ action, and returns the first record. sigh
Now, in your App A: users_controller, you want to set up a filter in the index like so:
def index
if params[:login]
# for single-sign-on.
@users = User.find(:all, :conditions => { :login => params[:login] })
else
@users = User.paginate(:all, :page => params[:page]) #...
end
respond_to do |format|
format.html
format.json { render :json => @users }
end
end
Do you have a better way of doing this?
9 Responses to “Authenticate like SSO with ActiveResource”
Sorry, comments are closed for this article.
July 18th, 2008 at 06:33 PM
CAS works fine. And Users won't have to login again and again when they change the app. Plus, you also get single click logout.
What you've done is not really SSO. It's more like "oh hey, same username/pass will work everywhere"
July 18th, 2008 at 07:34 PM
HTH,
http://www.infoq.com/news/2008/07/gehtland-security-and-identity
This is the route I would take.
July 19th, 2008 at 03:57 AM
It's been a while since I last used ActiveResource, but I'm pretty sure it still works the same way. If you want your request to route to the show action in the backend, you'll need to do something like this:
And then you can get it through params[:id] on the other side.
/Jonas
July 19th, 2008 at 08:14 AM
I've been tinkering with something similar for the last month or so, since seeing Justin Gehtland's talk at RailsConf. I'm not interested in using CAS, because, well, I'd rather not set up a Java server if I don't have to.
I've done the bit about delegating the user model via ActiveResource, and gone one further.
If all the applications you're concerned with are hanging off the same domain, you can skip the multiple signon issue by telling your app to stash an authentication token cookie at the top of your domain structure. That cookie will get sent to every subdomain, and you can pull the auth information from that.
Of course, that breaks if the user has cookies disabled...
July 19th, 2008 at 09:34 AM
If you google, you'll find a CAS server written in camping. It'll be about a day work to convert that into a rails app. I've done it, but cannot OSS it.
July 19th, 2008 at 04:16 PM
You might try rubycas-server. I got it working just fine on a centos vm for testing purposes...never got the change to implement it in production. (http://code.google.com/p/rubycas-server/wiki/HowToConfigure)
July 20th, 2008 at 09:47 PM
This way you still have to provide a login-page and deal with signo-ins from multiple sites, registration, login-errors and so on - so it does not really feel like SSO. I found CAS to be quite an overkill, especially if you already have an existing user-management. There is a poor-mans SSO that uses a simple sequence of redirects to get a logged in user: http://www.vierundsechzig.de/blog/?p=25
We're currently using this with a fat website in the middle and some satelite applications that delegate to the central website - this is easier than CAS but still get's completely rid of all login-related tasks in your client websites....
July 21st, 2008 at 12:20 AM
We use a request to /users/me to authenticate someone. Something like this:
User.user = login User.password = password begin User.get(:me) rescue ActiveResource::UnauthorizedAccess nil end
Unfortunately ActiveResource doesn't instantiate records on non-standard resources, so you will have to hack around that a little bit.
July 21st, 2008 at 09:12 AM
I'll also vote for RubyCAS, it took a little hacking, but we were able to hook it up to Beast so that our forums could use the same login credentials as the application.