Multiple Applications with Devise, Omniauth and Single Sign On

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:

require 'omniauth/oauth'
require 'multi_json'

module OmniAuth
 module Strategies
  class MyStrategy < OAuth2

   def initialize(app, api_key = nil, secret_key = nil, options = {}, &block)
     client_options = {
      :site =>  'http://myserver.local',
      :authorize_url => "http://myserver.local/auth/my_strategy/authorize",
      :access_token_url => "http://myserver.local/auth/my_strategy/access_token"
     }
     super(app, :my_strategy, api_key, secret_key, client_options, &block)
   end

protected

   def user_data
     @data ||= MultiJson.decode(@access_token.get("/auth/my_strategy/user.json"))
   end

   def request_phase
     options[:scope] ||= "read"
     super
   end

   def user_hash
     user_data
   end

   def auth_hash
     OmniAuth::Utils.deep_merge(super, {
       'uid' => user_data["uid"],
       'user_info' => user_data['user_info'],
       'extra' => {
         'admin' => user_data['extra']['admin'],
         'first_name' => user_data['extra']['first_name'],
         'last_name' => user_data['extra']['last_name'],
       }
     })
   end
  end
 end
end

Now, in my config/initializer/omniauth.rb I can add

Rails.application.config.middleware.use OmniAuth::Builder do
 provider :my_strategy, APP_ID, APP_SECRET
end

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)
  • Register the client applications (key and secret) via rails console on User Manager.
class AuthController < ApplicationController
 before_filter :authenticate_user!, :except => [:access_token]
 skip_before_filter :verify_authenticity_token, :only => [:access_token]

 def authorize
   # Find the Application using params[:client_id]
   # redirect to params[:redirect_uri]
 end

 def access_token
   # Check the params[:client_secret]
   # return Access Token
 end

 def user
   # Create the hash
   # return json hash
 end

Note: we are using a few of Devise before filters to authenticate the users before authorizing any application!

My User model had these Devise modules loaded:

class User < ActiveRecord::Base
 devise :database_authenticatable, :registerable,
        :token_authenticatable, :recoverable,
        :timeoutable, :trackable, :validatable

Now, the trick was to configure the User Manager with the standard omniauth providers. Here is the configuration:

Rails.application.config.middleware.use OmniAuth::Builder do
 provider :twitter, ApplicationConfig['TWITTER_APP_ID'], ApplicationConfig['TWITTER_APP_SECRET']
 provider :facebook, ApplicationConfig['FACEBOOK_APP_ID'], ApplicationConfig['FACEBOOK_APP_SECRET']
end

Now, when a user  logs in or signs up from App1:

  • the request is redirected to the UserManager via omniauth route ‘/auth/my_strategy’.
  • The User Manager signup can further redirect it to Twitter or Facebook using ‘/auth/twitter’ or ‘/auth/facebook’  and get the user to login / signup.
  • The request is redirected back to App1 via User Manager oauth provider callback uri.

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.
  • User Manager checks and finds a valid token and returns this to App2.
  • The user is automatically signed into App2.

Aim achieved! Seamless single-sign-on between multiple applications.

Update 1

I have extracted the code for this and have open-sourced it (finally)

The provider is at https://github.com/joshsoftware/sso-devise-omniauth-provider

The client is at https://github.com/joshsoftware/sso-devise-omniauth-client

I have updated the README for detailed instructions.

Update 2

Added account linking support. So if a user registers via Twitter and later tries to login via Facebook – he can link both these user accounts!

Update 3

Github code base is now updated to Rails 3.1.3. Thanks @robzolkos for helping out. The Rails30 branch is available for older the version.

Feel free to ask questions and give feedback in the comments section of this post. Thanks and Good Luck!

About these ads

About Gautam Rege

Rubyist, Entrepreneur and co-founder of Josh-Software - one of the leading Ruby development shops in India.
This entry was posted in Ruby on Rails and tagged , , , , , . Bookmark the permalink.

121 Responses to Multiple Applications with Devise, Omniauth and Single Sign On

  1. Pingback: Tweets that mention Multiple Applications with Devise, Omniauth and Single Sign On « Josh Software – Where Programming is an Art! -- Topsy.com

  2. Pingback: Link dump for December 16th | The Queue Incorporated

  3. Sylvain says:

    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 !

  4. Pingback: Devise and OmniAuth for Single Sign On « While I Pondered…

  5. Gautam Rege says:

    Guys,
    I finally found some space and pushed the code to github!

    The provider is at https://github.com/joshsoftware/sso-devise-omniauth-provider

    The client is at https://github.com/joshsoftware/sso-devise-omniauth-client

    I have updated the README for detailed instructions.

    • Paul Nelligan says:

      Hi Gautam,

      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.

      I will continue to investigate.

      thanks again

      Paul

      • Gautam Rege says:

        Hey Paul,
        Whats the logs say? I am pretty sure you have a filter_chain_halted somewhere in the request. Do paste the log output here, it would be easy to investigate and tell you whats going wrong.

        Cheers!
        – Gautam

      • Paul Nelligan says:

        Hi Gautam, thanks very much for your reply.

        I have determined that it’s failing on the first line of ‘sessions/create’ in devise:

        resource = warden.authenticate!(:scope => resource_name, :recall => “#{controller_path}#new”)

        as for the server output, it’s nothing unusual, but I’ve posted it here:

        http://pastie.org/1563391

        cheers

        Paul

      • Paul Nelligan says:

        just one more thing, the problem still exists with the provider app in standalone mode. thanks again

      • Gautam Rege says:

        Gotcha!

        I have updated the source code and pushed the chantges!

        The config/initializers/devise.rb had the authentication key set as ‘username’. I commented that line to ensure that by default it was email.

        The omniauth continues to work as it bypasses the default authorization.

        Thanks for finding the bug.
        – Gautam

      • Paul Nelligan says:

        perfect!, thank you..

  6. roberto says:

    Tks again Gautam

  7. Martin says:

    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?

    Martin

    • Gautam Rege says:

      Martin,
      Thanks – your comment very comforting! :)

      I used OAuth2 because its the latest standard – and probably the lastest and most comprehensive. You can use OpenID and CAS too but then why not move among the latest stuff ;)

  8. Pingback: Integrate Gigya Register Widget into your Application « Josh Software – Where Programming is an Art!

  9. Pingback: Want to increase user registrations using all available social networks? Gigya widgets are quick and easy! « Josh Software – Where Programming is an Art!

  10. Rouven says:

    Big thanks for this nice tutorial and the demo code!

    But i can only get the client working with omniauth version ‘0.1.6’. If I use the current omniauth version (0.2.1) i always get ‘message”=>”invalid_credentials’

    Is there an update needed for the provider strategie?

    • Rouven says:

      Ok…i found the problem! :) … I just needed to change the ‘access_token’ to ‘oauth_token’ in the ‘user’ model! *damn*! :)

      Server file: app/models/user.rb

      FROM:
      self.token_authentication_key = “access_token”

      TO:
      self.token_authentication_key = “oauth_token”

      It´s needed because Omniauth version >= 0.2.1 is using the >= ‘OAuth2 draft 10′ specification where it has to be called ‘oauth_token’.

      • Gautam Rege says:

        Good to hear this and thanks for updating it here.

      • Ricardo says:

        Thanks for this great post.

        But I have the following error message in my client: invalid_credentials

        Im using omniauth 0.2.5

        You figured out how solve this problem?

      • Rouven says:

        You already changed

        self.token_authentication_key = “access_token”

        to

        self.token_authentication_key = “oauth_token”

        in your ‘app/models/user.rb’ file? This fixed my problem..

        Best regards
        Rouven

      • b7ue9lank says:

        I did this but still get `invalid_credentials`

  11. JHafliger says:

    Hi, Excellent work josh, i already use and works great, but i have a question, it is possible do logout in all clients at same time?

    JHafliger

    • Gautam Rege says:

      Yes – you can. Keep the secret token (config/initializers/secret_token) the same for all the clients. It will destroy the rack-session for all the SSO clients.

      Do check the github issues for the repositories – its mentioned there.

      • Cliff says:

        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?

      • Cliff says:

        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?

      • Gautam Rege says:

        @Cliff
        Session sharing and SSO are different things. SSO requires sessions sharing.

        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.

      • Cliff says:

        @Gauntam – Thanks for the reply. Maybe I’m misunderstanding what you’re saying so let me explain a bit more.

        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.

      • Gautam Rege says:

        @Cliff

        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.

        You may be leaning towards a CAS setup for your work (IMO)

  12. Jørgen says:

    Hi gautam, this looks like a great way to accomplish what I want to.

    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 :)

    Cheers :)

    • Jørgen says:

      Here’s the last part of the development.log of the provider, if that helps :)

      http://www.pastebin.com/AA4itVML

      I noticed that the access_token is empty in some of the requests, but I don’t know if that’s intentional.

      Cheers

    • Gautam Rege says:

      @Jørgen I have seen this looping happen when the client application is not registered with the provider. You need to do that via rails console.

      In all likelyhood, login_required routine redirects back to /auth/josh_id but since the client is not registered with the provider, its returning ‘Invalid Credential’.

      In case you have registered the application, do let me see some code, I shall help you resolve the problem.

      • Jørgen says:

        Thanks for the quick reply, and you were correct of course, I had forgotten to add the Client to the Provider :)

        I’m sure I’ll have more questions as I play with this some more, thanks again for taking the time to respond :)

        Cheers!

  13. Jørgen says:

    Hi again Gautam, another question :)

    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:

    This is the client:

    OauthClientDemo::Application.config.session_store :cookie_store, :key => ‘_oauth-client-demo_session’

    if ENV['RAILS_ENV'] == ‘production’
    OauthClientDemo::Application.config.session_store :cookie_store, :key => ‘_myapp_session’, :domain => ‘.psdrefill.com’
    else
    OauthClientDemo::Application.config.session_store :cookie_store, :key => ‘_myapp_session’, :domain => ‘.rails.localhost’
    end

    The provider:

    OauthProviderDemo::Application.config.session_store :cookie_store, :key => ‘_oauth-provider-demo_session’

    if ENV['RAILS_ENV'] == ‘production’
    OauthProviderDemo::Application.config.session_store :cookie_store, :key => ‘_myapp_session’, :domain => ‘.psdrefill.com’
    else
    OauthProviderDemo::Application.config.session_store :cookie_store, :key => ‘_myapp_session’, :domain => ‘.rails.localhost’
    end

    These are my three sites:

    http://www.psdrefill.com (provider)
    http://www.uirefill.com (client – app1)
    http://www.shaperefill.com (client – app1)

    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 :/

    Oh, all three sites are on Heroku, by the way.

    Thanks for any help, I really appreciate it :)

    Cheers,
    Jørgen

    • Gautam Rege says:

      Try setting the :domain => :all in session_store – that should make it work.

      From what I see, this would work in development (as you have the same host for all apps) but in production you can specify only :domain.

      When you use subdomains, you should set it to ‘.psdrefill.com’

      When you use different domains, the easiest fix is :all

      • Jørgen says:

        Thanks Gautam, but it didn’t seem to help :(

        When I sign in I get the Heroku error “The change you wanted was rejected. Maybe you tried to change something you didn’t have access to.”

        My config line now looks like this:

        OauthClientDemo::Application.config.session_store :cookie_store, :key => ‘_rnapp1234_session’, :domain => :all

        – does the key matter? I’ve used the same key in both apps

        Thanks :)

      • Gautam Rege says:

        This is usually a coding error – especially when security exceptions (like invalid authenticity tokens or forgery issues) are raised.

        Try this:
        – Restart heroku apps
        – Clear cache and the cookies (maybe the session key is conflicting)

        CHECK LOGS ! :)

      • Jørgen says:

        Hmm, I’ve emptied the cache, restarted the apps and tried other browsers as well. Single sign-out still isn’t working.

        Are there other ways of triggering sign out in all of the apps/domains?

        I had an idea to just have iframes pointing to each domain’s /logout on the page the user comes to when they sign out on one of the domains – but it isn’t very elegant :)

    • Gautam Rege says:

      Jørgen,
      Hang on! So the heroku error is not happening now? Thats good.

      Check your configuration or paste a snippet (pastie link) of the 3 apps and their config/sess* files.

      • Jørgen says:

        Thanks Gautam, here is the code: http://pastie.org/2036269

        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!
        :)

      • Gautam Rege says:

        I hope you have SAME secret_token for all the apps (provider and clients). You will find that in config/initializers/secret_token.rb

        I just checked out the URLs mentioned above – you have the iframe solution working — its not elegant — can you revert it so that I can see the problem happening.

      • Jørgen says:

        I’ve reverted to the old code. I’m using the same secret_token in both the provider and client apps :)

  14. tjstankus says:

    I also encountered a redirect loop after setting up and configuring the client and provider apps. The client app was registered with the provider. The issue turned out to be related to a change in the oauth2 gem (http://bit.ly/iQc9VW) as explained in this blog post: http://ryanbigg.com/2011/04/whodunit-devise-omniauth-oauth-or-github/

    • cao7113 says:

      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!

  15. b7ue9lank says:

    I’m trying to use this and getting stuck in a redirect loop:

    https://github.com/blueblank/sso-devise-omniauth-provider/issues/2

    At this point the client gets to
    /auth/generic_id/callback?code=40b17b43c40e2db9088ac41ba4fff53c&response_type=code
    then
    /auth/failure?message=invalid_credentials

    • b7ue9lank says:

      After going through these comments:
      – changed access_token to oauth_token in the model
      – double checked the client is registered with the provider

      still looping, though

  16. Gautam Rege says:

    Just commented on issues/2 in github. I think the application is not registered properly – at least the logs imply so. can you recheck that?

    • b7ue9lank says:

      So how do I register the application properly? I thought I was but there is something I’m missing.

      • Gautam Rege says:

        Nothing out of the ordinary:
        APP_ID = ‘YE0NYveQGoFsNLX220Dy5g’
        APP_SECRET = ‘aqpGBedDnHFyp5MmgT8KErr9D015ScmaY8r3vHg5C0′

        Provider console> ClientApplication.create( :name => “MyApp”,
        :app_id => APP_ID,
        :app_secret => APP_SECRET
        )

        Client configuration: /config/initializers/omniauth.rb
        Rails.application.config.middleware.use OmniAuth::Builder do
        provider :josh_id, APP_ID, APP_SECRET
        end

        Route: /auth/josh_id

  17. Dora says:

    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.

    • Gautam Rege says:

      Hi Dora,
      I think you found a bug. save_referrer is a stagnant method and we can remove it now.

      I was trying stunt to save ‘return path after login’ but I you dont really need it for ‘edit’. So, just remove it. Registrations#edit would be used for things like profile management.

      • Dora says:

        Thanks a lot.

        This SSO solution is the best for now.
        I hope you keep updating for rails 3.1.0 and more.

        Cheers :)

  18. Mike says:

    Hello Gautum.

    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.

    Any help would be greatly appreciated!!
    Thanks.

    • Gautam Rege says:

      Hi Mike,
      I’ll check on override of omniauth methods — it seems the way to go — but I guess you would also have to configure provider routes.

      Can you paste a snippet of what your config — I can try to simulate it.

      • Mike says:

        Thanks for replying. I am trying to deploy your provider under apache/passenger and the routes file is your basic setup. I have tried to wrap the routes in

        scope “/suburi”
        do

        but that doesn’t seem to help. It actually looks like a request is made to “/suburi/auth/strategy” which is correct but then an error is thrown:

        ActionController::RoutingError (No route matches “/auth/strategy”)

        That path seem to be hardcoded within omniauth or devise.

        I notice that only your client has an omniauth.rb in the initializers directory. Can the provider also have this file? We may be able to set some things in there.

        Thanks.

      • Gautam Rege says:

        Hi Mike,
        The provider is an omniauth client too – infact thats how oAuth via twitter and facebook kick in for clients too.

        I wonder if we can configure our provider route with the specific suburi – hmm. need to think about this :)

  19. zchaudhry says:

    superb work.

    can u please give some guidelines on how the flow will work if the client opens the provider in a lightbox for login/signup.

    • Gautam Rege says:

      As I see it, there are plenty of redirections involved in OAtuh – 3 way handshake – so this would not be a good choice for a lightbox.

      I’ll try some stunts and try to come up with a solution.

  20. Mike says:

    Well I have tried adding suburi to the routes but something in omniauth still tries to use /auth.
    We need a way to configure omniauth to prefix /auth with /suburi. Not sure how. Thanks.

  21. alexirys says:

    You could also show how to sign requests to provider’s API with the user that is signed in.

  22. Dora says:

    Hello,again.

    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.

    Regards.

  23. Pingback: nSpaces 1.1.0.2 | Daily Freeware Download

  24. Ed says:

    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.

    Thanks.

    • Gautam Rege says:

      @Ed,
      That is exactly what the oauth provider is :)

      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.

  25. Ed says:

    Thanks Gautam for the guidance and for the many great blog posts.

  26. Pingback: Alec C4 » Blog Archive » Linkoholizm #7

  27. Bastien says:

    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!

    • Gautam Rege says:

      @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.

      • Bastien says:

        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?

      • Gautam Rege says:

        If you want a different view for App1 and App2, you can fetch the user data from UserManager using user.json and then render the info in your own way specific for App1 or App2.

        In case of edit, you should set the form url to post the data to UserManager app and send a redirect_url as one of the parameters, so the views are consistent after update.

  28. PJ Cabrera says:

    Great post and great feedback to readers with questions! Thanks for sharing the implementation on Github.

  29. Pingback: Ruby on Rails – Ubuntu 11.10 « indykish

  30. D B says:

    Excellent guide!
    Question for you guys:

    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?

    Thanks!

  31. gady says:

    Is there a setup that app1 holds the user model and gives authentication services to app2?

    • Gautam Rege says:

      @gady – not in this repos. But like I said in the earlier comment, you can use RABL to build an API service for this – though it would be a little network intensive.

  32. klissrussia says:

    Man, you rock!

    Many thanks for this great post, it sown some thoughts in my head :)

  33. thil says:

    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?

    Thanks
    thil

    • Gautam Rege says:

      @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.

  34. Pingback: Quora

  35. Drew says:

    I tried changing:

    self.token_authentication_key = “access_token”
    to
    self.token_authentication_key = “oauth_token”

    But I still get the `invalid_credentials` error. Did anyone find a fix by any chance?

  36. andy says:

    Hi – first of all, thanks for the great piece of software !

    fyi – I had to change the database.yml to work with rails 3.2.6 a little bit.

    the replacement for device:

    ## Database authenticatable
    t.string :email, :null => false, :default => “”
    t.string :encrypted_password, :null => false, :default => “”

    ## Recoverable
    t.string :reset_password_token
    t.datetime :reset_password_sent_at

    ## Rememberable
    t.datetime :remember_created_at

    ## Trackable
    t.integer :sign_in_count, :default => 0
    t.datetime :current_sign_in_at
    t.datetime :last_sign_in_at
    t.string :current_sign_in_ip
    t.string :last_sign_in_ip

    thx a lot,
    andy

  37. abhishek says:

    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?

  38. abhishek says:

    i m using device’s sign_out

  39. abhishek says:

    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?

    • Gautam Rege says:

      @abhishek,
      I tried my best to understand what you have written – I could not understand anything. Could you comment again in plain english — not sms style please :)

  40. logesh says:

    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 is
    http://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

    • Gautam Rege says:

      @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.

  41. logesh says:

    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

    class PasswordsController :edit

    def new
    super
    end

    def create
    super
    end

    def edit
    self.resource = resource_class.new
    resource.reset_password_token = params[:reset_password_token]
    end

    def update
    self.resource = resource_class.reset_password_by_token(resource_params)

    if resource.errors.empty?
    flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
    set_flash_message(:notice, “New password has been saved”)
    redirect_to new_user_session_path
    else
    respond_with resource
    end
    end

    protected

    # 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

    # Check if proper Lockable module methods are present & unlock strategy
    # allows to unlock resource on password reset
    def unlockable?(resource)
    resource.respond_to?(:unlock_access!) &&
    resource.respond_to?(:unlock_strategy_enabled?) &&
    resource.unlock_strategy_enabled?(:email)
    end
    end

    and my routes is

    devise_for :users, :controllers => { :passwords => ‘passwords’ }

    and the error i get is

    NameError in PasswordsController#update
    undefined local variable or method `resource_params’ for #

    • Gautam Rege says:

      @logesh – Devise signs out a user when the password is changed. This is the default behavior and you don’t need to do anything special !

      • logesh says:

        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.

  42. Gautam Rege says:

    @logesh – Ah! now I understand what you are doing :)

    Not tested this yet but try changing the redirect_to line to:
    redirect_to new_session_path(resource_name)

    If that still fails change it to:
    respond_with resource, :location => new_session_path(resource_name)

    If that fails, I’ll try some stunts locally and let you know

  43. logesh says:

    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>

    def update
    self.resource = resource_class.reset_password_by_token(resource_params)

    if resource.errors.empty?
    flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
    set_flash_message(:notice, “New password has been saved”)
    redirect_to new_user_session_path
    else
    respond_with resource
    end
    end

  44. logesh says:

    I have pasted the passwords_controller file in
    Public Clone URL: git://gist.github.com/4124530.git

    • Gautam Rege says:

      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.

  45. logesh says:

    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])

  46. logesh says:

    I use devise and so in my route file i have devise_for :users, so when i click on the sign up link the url looks like

    http://localhost:3000/users/sign_up

    so i thought of removing users from link and just sign_up would be enough so i changed my routes as

    devise_for :users, :path => ‘signup’, :path_names => {:sign_up => “new”} which now makes the url as

    http://localhost:3000/signup/new

    and similarly added
    devise_for :users, :path => ”, :path_names => {:sign_in => “login”, :password => “password”}
    and the url for sign in changed to

    http://localhost:3000/login

    but when clicking on forgot password link the url is

    http://localhost:3000/signup/password/new

    but i want it to be
    http://localhost:3000/password/new How can i do this? How should i change my routes to get it?

    • Gautam Rege says:

      @logesh,
      You have 2 paths defined. The way I see it:
      devise_for :users, :path => “”

      This gives the routes:

           new_user_session GET    /sign_in(.:format)                       devise/sessions#new
                   user_session POST   /sign_in(.:format)                       devise/sessions#create
           destroy_user_session GET    /sign_out(.:format)                      devise/sessions#destroy
                  user_password POST   /password(.:format)                      devise/passwords#create
              new_user_password GET    /password/new(.:format)                  devise/passwords#new
             edit_user_password GET    /password/edit(.:format)                 devise/passwords#edit
       cancel_user_registration GET    /cancel(.:format)                        devise/registrations#cancel
              user_registration POST   /                                        devise/registrations#create
          new_user_registration GET    /sign_up(.:format)                       devise/registrations#new
         edit_user_registration GET    /edit(.:format)                          devise/registrations#edit
      

      HOWEVER, I would not recommend this — see “/edit(.:format)” .. really ??

  47. Hello,

    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?

    Thanks.

    • Gautam Rege says:

      @KyleSziv
      From the looks of it, it seems that when you call /user.json method, you are not passing the access_token. That’s why its giving an error.

      This is the request that should go the provider for /user.json:

      /auth/josh_id/user.json?oauth_token=#{access_token.token}”

      If this is correct, some code snippets and logs snippets would help debug this .(pastie or gist).

      • @Gautam

        It appears that I don’t make it to user.json at all. Maybe these code snippets will make more sense than me describing it:

        The josh_id.rb file is the same as what you have in our example on github (except it uses my provider’s url).

        Any help is much appreciated. Thanks.

  48. Gautam Rege says:

    @KyleSziv — found something interesting that may be the cause of the invalid credentials.
    https://github.com/intridea/omniauth-oauth2/pull/18 talks about “state” parameter to prevent CSRF.

    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. :)

      • Gautam Rege says:

        Nice!
        However that does also means that an upgrade to the latest omniauth breaks the system and we have to adapt to that.
        I have file an issue#19 on github, so that we can fix it.

        Do provide more details there that could help.

      • abhishek says:

        Is there any way so that i can store each user’s id against his session_id in new table called user_session in rails 3???

  49. logesh says:

    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

  50. Pingback: Rails authentication across apps/servers | Ask Programming & Technology

  51. Ehsan says:

    Hi.
    After running “rake db:migrate” I get this output:

    C:\Sites\sso-devise-omniauth-provider>rake db:migrate
    rake aborted!
    uninitialized constant Devise::Models::TokenAuthenticatable
    C:/Sites/sso-devise-omniauth-provider/app/models/user.rb:7:in ‘
    C:/Sites/sso-devise-omniauth-provider/app/models/user.rb:1:in’

    C:/Sites/sso-devise-omniauth-provider/config/routes.rb:2:in block in ‘
    C:/Sites/sso-devise-omniauth-provider/config/routes.rb:1:in’
    C:in execute_if_updated’
    C:/Sites/sso-devise-omniauth-provider/config/environment.rb:5:in )>’
    Tasks: TOP => db:migrate => environment
    (See full trace by running task with –trace)

    I could not found any solution for it.
    I will appreciate if you could please guide me through this.
    Regards.
    Ehsan.

  52. 馮學正 says:

    Hi,
    I tried to implement https://github.com/gonzalo-bulnes/simple_token_authentication to original code but can’t make it, does anyone have done this? Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s