The best way to scale an application is to split the application business logic into different inter-communicable components. However, authenticating, authorizing and security raise concerns. OAuth comes to the rescue – and like a knight in shining armour – omniauth steals the show.

Omniauth is an awesome gem that allows you to authenticate using Open-Id based social networks. There are TONS of topics on this – the one I liked best was from RailsRumble. Devise was also integrating oauth and oauth2 into its authentication framwork when omniauth was released (in Oct, 2010). So, Devise dumped their oauth integration in their v1.2oauth branch and started integrating master with omniauth instead (:omniauthable Devise Module).

We wanted to solve these problems:

A single User Manager application (which will authenticate ALL users with different roles)

Different internal applications which talk to User Manager for authentication

User should be able to login/sign-up via Social Networks like Twitter and Facebook.

Single Sign On between all applications.

I found this wonderful post about how to implement an oauth provider in devise by Chad Fowler & Albert Yi. This does not use omniauth, though its almost there. So, I decided to merge these two approaches and what do you know — it works!

Our current setup:

User Manager: devise + omniauth

App1: omniauth + custom gem for omniauth custom strategy

App2: omniauth + custom gem for omniauth custom strategy

I followed the User Manager setup must like the blog mentioned above for implementing an oauth provider. To start with, I created a new strategy for omniauth that would be invoked from App1 and App2. We added a custom gem for easy deployment — but the essence of the code is here:

So, now we have the ‘OAuth Consumers’ or ‘OAuth Client’ (whichever you prefer to call it) for App1 and App2. The next step was to create the ‘Oauth Provider’ or ‘OAuth Server’. This we call the User Manager. You can follow instructions in the blog post mentioned above. I have skipped the details for the sake of brevity. In brief:

Create the standard devise User model and migration.

Create the Auth Controller actions (as show in the code snippet below)

Create the AccessGrant model (and if required the Authentication model)

This takes care of a single authentication system for the entire environment. Now we need to handle single sign on.

DO WE REALLY NEED TO? :) This is where devise+omniauth combo rocked! I had the custom omniauth strategy configured on App2 also. Now, if a user had logged in App1 and got authorized. In the same session (depends on how you have devise configured — I had :timeoutable and :token_authenticatable) when the user accesses App2:

I added a standard before filter called ‘login_required’ which redirects to User Manager via ‘/auth/my_strategy’ if there is no current_user.

This post is totally awesome ! However it’s quite hard to follow for me (not comfortable with oauth and devise). Do you think it could be possible to put the corresponding apps in a github repository for testing things in details ? In any cases, many thanks for this great post !

first let me thank you for this blog post, and for the link to the github repository.

I’m having a few small problems getting this working correctly. Firstly I installed your app and ran it on both client and provider sides. If I register, then I’m logged in across multiple clients. However if I log out, then I’m unable to log back in, and am simply redirected to the provider log-in page.

This is an awesome piece of work! I’ve been thinking about implementing something very much like this myself for a while. I too decided to go with Omniauth and Devise on the server and Omniauth on the client. I was planning to implement CAS between the client and server, but OATH should do fine. Any reason why you chose OAUTH as your internal protocol instead of CAS or OpenID?

I’ve tried going down the road discussed on the client repo issue list, but things seem odd. I’m using Apache locally with three domains, all in my hosts file:

sso.domain.dev
client1.domain.dev
client2.domain.dev

Everything works great until I try to tackle sign off. Adding the common secret token has zero effect (I believe this is because the token is only used for signed cookies and doesn’t actually have anything to do with which session is being used). Adding the session store :host just starts breaking things. I used sso.domain.dev for the :host across all three apps (provider + 2 clients). Doing so prevents any kind of log in to the provider. If I access the provider directly (not part of an oauth redirect) I’m just continually prompted to log in (no error messages shown). From my dev log it looks like login is successful, but seems like it can’t save the user into the session with the :domain set.

I’m pretty sure I’m just doing something wrong, but do you (or anyone else) have any more info on the topic or have you run across any “gotchas” that need to be taken into account?

Solved the issue. Apparently the :domain doesn’t like subdomains. All had to be set to ‘.domain.com’ instead of ‘sso.domain.com’

One concern though, doesn’t this mean that now actual sessions are shared between apps? So if you put something else in a session you can run the risk of overwriting data from another app using the same SSO service?

Session sharing is a common practice from very early versions of Rails. Since all apps on the wild-carded sub-domains are supposed to be trusted it is not a security issue. Furthermore, you never store sensitive information in the session cookies. Furthermore, these are usually encrypted cookies.

I guess I mentally just separate the idea of SSO (Singe Sign On) from session sharing. SSO (to me) simply means a single authentication mechanism with nothing to do with session sharing. I can use a single sign on service like OpenID and that is obviously not sharing a single session between every OpenID enabled site.

Now my issue emerges when trying to sign OFF. Would I be correct that the idea of SSO in and of itself does not handle or support a single sign OFF mechanism because to sign off you must end the session (not the scope of SSO)? I think the discussion so far has eluded to the idea that single sign on and off are both a unified idea but in reality Single Sign On is authentication and Single Sign OFF is session sharing. This is closer to my understanding of the separation between those two groups of functionality.

My concern over session sharing is not security, but practical limitations. Rails has a limit to the cookie session store and moving to another session store starts to impose limitations on the physical network of the machines (domains must be on the same box, or must access a shared DB server, etc…). It also rules out sessions from other legacy platforms or applications. Then you have my original concern which is simply overwriting data. If domain aaa.doman.com writes session[:next_url] = ‘/some/path/value’ and domain bbb.domain.com writes session[:next_url] = ‘/other/path/value’ then the value will be wrong when aaa.domain.com tries to read it.

In some situations, session sharing is probably ideal, but in mine, not so much. I have the advantage of starting with the requirement that all of my user models for all domains be held in a common DB table (same user data across all domains). I’m thinking a better approach is to have any explicit logout flag the user’s record and let me check user.last_logout for a date in the past and if it’s found, abandon the session manually.

There are server-side sessions and client-side sessions. When using devise the “logout” method will logout the user by destroying the warden.session and thereby destroying the cookie (client side session) too.

So, when you logout, it is genuinely a logout (part of the authentication process) and this *has* to be sent to the provider app after which the user is signed out. Since all the apps share the same key from the session_store.rb, their client session is also destroyed.

Now, if the client cookies were configured to work across multiple domains, the user is auto-logged of from all of them.

I think I’ve set everything up correctly, I’ve created one provider and one client (to begin with). On the provider, I can sign up and sign in and everything works smoothly.

When I access the client app it redirects to the provider to sign in, as expected. However, when I try to sign in (via the client app):
1. I fill out the sign in form
2. Get redirected back to localhost:3001/auth/josh_id (client)
3. See an error message from the browser saying that there have too many redirects – it appears to get stuck in a loop of some sort.

I do get signed in (because I’m signed in when I go to the provider site) but something isn’t working right with the redirection back to the client app.

I’ve changed the database from mysql to postgresql, but I don’t think that should have had an effect. Thanks for any help :)

I’ve got sign in working across 3 domains, one is the provider app, the other two point to the same client app. (my goal being to show slightly different content on the different domains but just using one app to keep things simple)

Now, I need to tackle single-sign-off. I’ve followed the instructions in the issue list, but it didn’t seem to work.

Here’s what I did:

1. Changed the secret token to be identical on the provider and client
2. Altered the session_store files:

I assumed that the provider-domain-URL should go in the provider and client’s :domain variable in the files above, but then visiting the clients sent the system into a loop again.

Then I changed the URL to be the url of the client (uirefill.com) and then the uirefill.com works fine in terms of SSO, but going to shaperefill.com (the same client app, just different domain) starts the loop again. If I sign out at the provider and go to the uirefill.com site I’m still logged in, so signing out does not seem to be working :/

I managed to find out when the heroku error occurs. It happens when I sign in as a user that has not previously been signed in. If I sign up as a new user (and automatically get logged in) I don’t get the error. Weird!:)

I also encountered a redirect loop in client side and checked it carefully. As tjstankus said, from version 0.2.1, OmniAuth depended on OAuth 0.2.0(bump from 0.1.1). There is a change in Oauth0.2.0(changed @token_param from ‘access_token’ to ‘oauth_token’), ref: https://github.com/intridea/oauth2/blob/v0.2.0/lib/oauth2/access_token.rb. That maybe the problem root. One solution I took is specifying exactly (gem ‘omniauth’, ‘0.2.0’). They works like past. Maybe someone solve this problem in future. I will appreciate it greatly!

Hi Gautam. I tried the provider app in your Github repo on rails 3.09 just by changing the gemfile.
It works properly almost all on rails 3.09, but I got error
‘undefined local variable or method `save_referrer’ ‘
in
before_filter :save_referrer, :only => :edit
of RegistrationsController.
Commented out this line, it works without error, but it could cause security holes.
I tried to search the method ‘save_referrer’ in files and web, i couldn’t get useful info about it.
Please show me where is the method defined in?
Thanks for your great works.

I had a question about putting the provider under a suburi for passenger. I cannot figure out how to get the path to be /suburi/auth/strategy instead of just /auth/strategy. I do not know where this path is coming from. Is there a way to fix this?

I read about a possible bug in omniauth and a quick fix was to override the request_path or add a path_prefix. But how do you set these?? My clients are also on suburi and their paths seem to work ok but the provider always tries to use /auth/strategy which is wrong.

The newest omniauth virsion 0.3.0 causes errors on this app. (gem ‘omniauth’ in gemfile without virsion)
The client app requests ‘http://localhost:3000/oauth/token?….’, but the server app has no route for the URI.
I added route.rb in the server app
match ‘oauth/token’ => ‘auth#access_token’
then the rooting error is gone, but the assumed query strings are not on the URI request(from client app). So the browser error ‘Request Roop’ happens.

The users who started or re-installed recently and got errors had better check the virsion of omniauth in gemfile.lock after bundle,
or you can easily avoid the errors by specifying the virsion of omniauth in gemfile before bundle.
I tried 0.2.6 in the client app, and it works good again.(on rails 3.0.9)

Don’t work hard to fix it. Relax.
OmniAuth1.0 will coming soon,and the protocol of oauth2 itself may still change.

Now that i have an oauth provider and six apps using it. I am wondering how to allow 3rd party app’s to access the api’s of all my six applications using a single oauth access token from thesame oauth provider that provides the single sign-on for the six applications. Something like using one facebook or google access-token to access all the various api’s provided by facebook and google.

All you need to do is the application registration process (automate it or do it manually). For example, you have to register applications with Twitter and FB before using it. When these applications are successfully registered, they get a token and secret. These are exactly the same as the Client records in this database for the Oauth provider.

If the Oauth provider generates these application tokens for 3rd party apps – they can login and then you can choose using some authorization tools like cancan along with Rabl to control the API access.

Hi Gautam,
First thanks for this great explanations, it helped me a lot figuring out the right set up for my auth system! I have one question in my mind for which I can’t find a good answer ; let’s say App1 and App2 need to access the UserManager’s User model to display & edit the current_user profile (first_name, last_name…), how would you implement such interaction? (JSON API, synchronisation of the User model in AppX and UserManager, user profile’s page hosted on the UserManager…) Again thank you very much!

@Bastien, If you synchronize data across your apps, you would end up with some crazy amount of complexity. The User model should *always* reside on the UserManager (I am guessing this is the omniauth provider for App1 and App2). In case you need to show / edit the user details, you can do this:

1. Post-authentication, you send the user.json request (as it shows in the sample app) and return the current details of the user – cache this information and show it in App1 or App2.

2. When you want to edit a user, simply redirect the user to /users/:id/edit Since there is SSO in place, you can easily move between applications and you can set a return_url in the session if required.

Thanks for your quick reply, it makes more sense now and SSO enables a seamless access to the UserManager’s data. ‘Step 2.’ would mean that the /users/:id/edit ‘s view is common to all Apps. If we wanted App1 and App2 to have a different design to show / edit the user profile I’m guessing that ‘Step 2.’ would not achieve that, am I right?

Say we have 3 Apps.
App1 is the user manager/oauth provider
App2 is a main app and oauth client
App3 is a utility app and 2nd oauth client

This solution works great for this situation.
How would one manage the case in which the user is logged in and using App2, but now App2 wants to communicate with App3 (at the backend level) to get some information?
I’m talking about the situation in which App3 has a REST API that App2 wants to call. Since it’s not going through the user’s browser I’d imagine there’s something special that needs to be done?

Hi,
Thanks for your post. Currently I am using the rails version 2.3.5 upgrading the those apps is big deal . To implement SSO as you mentioned need to upgrade the rails or still can use with some modification?

@thil My earliest implementation was 3.0.3 :) So, I recommend you check on forks on the github repos to see if someone has done this for Rails 2.3 – I personally have not tried it.

Omniauth and Devise are not dependent on any version of Rails (or its dependencies), so I think this should work if we downgrade to Rails 2.3. Just ensure that you are using Rack > 1.0, I’m pretty sure that is required.

If you fork and have a setup for this, I would be glad to merge that into a branch.

if a user with unique user_id is logged in different browser at the same time (ie logged in in firefox,IE,chrome and in safari). and if I use sign_out @user then it will log out the user only from the respective browser from where sign_out @user is called , same user on the other browser are still logged in. Any solution?

Hi gautam ,
thx for ur last post.
nw i m stuck in another issue. its like i m using SSO and der is session expired time out set on sso portal den a request is send to my dependant application which uses SSO portal fog logging in dat is der any sesssion related to dat user is active or nt if no session for dat user den session gets expired from SSO portal and if any session related to dat user is still active on dependant application den it wont allow to destroy the sso portal session of dat user. so when i get a specific requst from sso portal i want to knw details of how many session are active for dat user on my dependant application?

I have a web app where i have used devise and omniauth for authentication and now i want to use this for iphone app. Now the user can send the email and password if he has signed up using devise and it returns the authentication token and email id of the user where the user can use the token and make the subsequent requests but how can i allow the facebook users to make requests similarly. I have searched and could not find how to do this? Only thing i found useful ishttp://stackoverflow.com/questions/4623974/design-for-facebook-authentication-in-an-ios-app-that-also-accesses-a-secured-we but still do not know how to do the one that is explained there.
So pls help me

@Logesh This blog post talks about exactly this but using a web client, not a phone client. If your iphone app is a web-client, you can configure the Provider to talk to social networks like twitter and facebook as shown in this post and the users can sign_up / sign_in using them — and get their tokens setup via Omniauth. You cannot get users to sign up on the provider via email and the app their FB token – it will be rejected.

On the other hand, you may have to get signed-in users to “add FB authentication” to your app and then save the long-lived token in the database. That however has its own complexities.

Not sure if this helps but if you give me some more details, I could investigate.

I have problems when overriding passwords controller in devise. I do not want to sign in the user after password is changed so i thought of overriding the password controller and i tried as follows and got an error. I could not identify what the problem is, so please help me. The following is the passwords_controller.rb

# The path used after sending reset password instructions
def after_sending_reset_password_instructions_path_for(resource_name)
new_session_path(resource_name)
end

# Check if a reset_password_token is provided in the request
def assert_reset_token_passed
if params[:reset_password_token].blank?
set_flash_message(:error, :no_token)
redirect_to new_session_path(resource_name)
end
end

But when i click the forgot password it asks for email id and after giving the email id it sends the reset password instruction to the mail and when clicking on the link it asks for new password and confirm password and after giving it i logs in the user and i want to avoid this.

My update method is as follows and it does go for redirect and it shows error in the first line itself
NameError in PasswordsController#update
undefined local variable or method `resource_params’ for #PasswordsController:0x000001034501b0>

Your code seems correct – there does not seem to be any reason resource_params should not be found. Can you check the rest of your code for existence of another PasswordsController class?

Also, you don’t need to copy paste all the data into your PasswordsController… it should ideally contain only the ‘update’ method — you don’t need to copy the rest of the code. I’ll play around with a sample app and check this.

Finally it works. I am using devise version 1.5.3 and it does not provide resource_params method so i copied the following from devise version 1.5.3 and it works. self.resource = resource_class.reset_password_by_token(params[resource_name])

I’m having some difficulties getting this configured into one of my apps. I have a main application which also serves as the provider. Then I have another app that serves as the client. I have the josh_id.rb file configured with the proper domains. In the client’s application controller I have the login_required method and the before_filter and when the user goes to the index of the client, it will redirect to /auth/joshid. The method in the auth_controller that properly gets called the is access_token. This method properly authenticates the application and authenticates the access grant. However, my provider application says on the user method that access is denied and responds back to the client application that the user needs to log in. In the database, Access Grants table has the proper user id of the user I used to log into the system. I’m not sure what is wrong. Do you have any advise?

I saw the ‘state’ parameter in your callback and started investigating a little. I see that you are using the omniauth-oauth2 (v1.1.1) gem for the OAuth2 strategy. I wonder thats what’s causing the trouble – not sure yet though.

Can you downgrade omniauth to v1.1.0 and see if the problem persists? If it doesn’t, it means we need to upgrade our provider code to handle the “state” and in general upgrade to omniauth v1.1.1 :)

It appears that solved a lot of the issues I was experiencing. Now it just gives a MySQL error saying that authorization_token column is not in the user table. I added an additional line in the authorize method and it allowed me to the client.

I think it’s at a pretty good point of success. I really appreciate all your help with this. :)

I have a user model and language model where the language model contains list of languages and the user model contains list of users and i want the user to select a language and that would be users native language. I am using this for iphone app so when the user calls an api the list of languages will be sent and from that the language id has to be passed and it should assign the language for the user. How can i do this? Thanks in advance