31

I've been developing my rails apps whilst keeping them as modular as possible. I'm trying to implement different parts underneath as services.

Say an example of Facebook:
a) A MainApp that allows the user to have a wall, posts, etc.
b) A PhotoApp that stores photos, allows the user to see his photos, etc. This is a standalone app that will have a REST API that can be used by MainApp as well.

I was thinking of using OAuth as a Single Sign On solution (as in this tutorial http://blog.joshsoftware.com/2010/12/16/multiple-applications-with-devise-omniauth-and-single-sign-on/) where each app will be authorized via OAuth and will get access to the current user session based on the cookie.

First question: Is this a viable solution?

Second question: I want to be able to call the PhotoApp API from the MainApp server (not from the user's browser). How would authentication work in this situation?

Third question: How would this work if say I had a service that used node.js?

Fitzsimmons
  • 1,411
  • 1
  • 10
  • 17
RailsN00B
  • 313
  • 3
  • 5
  • There are a few [Oauth Servers](http://www.knight.io/categories/oauth-servers-ruby) that might help in implementing such a scenario. – Thomas Klemm Nov 04 '12 at 19:31

3 Answers3

20

Yes, SSO using OAuth is a viable solution, but it's not the simplest one. When building anything new, OAuth 2.0 is the way to go. The OAuth standards cover a lot of ground.

The primary advantage of OAuth is that it allows users to give 3rd party apps access to their account without disclosing their password to the 3rd party. If you are not seriously providing such interoperability, then OAuth is probably overkill.

Given the complexity, I offer a different pair of solutions:

For Single Sign On

The trick is to share the session ID cookie between hosts within your domain & to use a shared session store (like ActiveRecordStore or a cache-based store.)

Every Rails app has a "secret" that is used to sign cookies. In newer Rails apps this is located in /config/initializers/secret_token.rb. Set the same secret token in each application.

Then, configure the session to allow access from all subdomains:

AppName::Application.config.session_store :active_record_store, :key => '_app_name_session', :domain => :all

For Internal API calls

Use a good shared secret to authenticate over HTTPS connections. Pass the secret in the "Authorization" header value.

You can use the shared secret easily with other architectures (like node.js). Just make sure you always use HTTPS, otherwise the shared secret could be sniffed on the network.

Mars
  • 1,530
  • 10
  • 15
  • 1
    Sharing a session ID seems like a good start to SSO, but it stops there. There's a large amount of implementation details left out here. How does an app authenticate during login? How does the app learn about the capabilities of a signed-in user? – Fitzsimmons Nov 08 '12 at 00:26
  • 1
    Yes, there's a lot to design when implementing a complete SSO solution. Creating a central authentication service is probably a good start, but it's beyond an answer on StackOverflow... probably more like an O'Reilly book. – Mars Nov 08 '12 at 05:49
  • This assumes that all domains share a common tld/subdomain. What about different tlds? – lukad Apr 23 '15 at 13:28
  • This answer does not "assume" that the servers are all in the same domain, it states that fact explicitly (under "For Single Sign On.") If the servers are spread across different domains, then you'll need to look into more sophisticated solutions such as OAuth or SAML-SSO. – Mars Apr 24 '15 at 21:56
5

You could look at a Service Oriented Architecture solution as proposed by Jeremy Green at Octolabs during the 2014 RailsConf.

The blog post with all the resources (repos, demos, etc.) is located here: http://www.octolabs.com/so-auth

And the video that explains everything is here: http://www.youtube.com/watch?v=L1B_HpCW8bs

This centralized SSO is no simple task but Jeremy has done an excellent job talking about Service Oriented Architecture and sharing exactly how you might put this system together.

George
  • 589
  • 5
  • 6
3

I recently had a similar problem of wanting to share session data between Rails and an Erlang app. My solution was to write a Rack::Session::Abstract::ID class that stored sessions in Redis as hash vaules. It doesn't call Marshal.dump on String types. This allows non-ruby applications to use some of the session values if they have the session_id.

require 'rack/session/abstract/id'

class MaybeMarshalRedisSession < Rack::Session::Abstract::ID

  def initialize(app, options = {})
    @redis  = options.delete(:redis) || Redis.current
    @expiry = options[:expire_after] ||= (60 * 60 * 24)
    @prefix = options[:key] || 'rack.session'
    @session_key = "#{@prefix}:%s"
    super
  end

  def get_session(env, sid)
    sid ||= generate_sid
    session = @redis.hgetall(@session_key % sid)
    session.each_pair do |key, value|
      session[key] = begin
        Marshal.load(value)
      rescue TypeError
        value
      end
    end

    [sid, session]
  end

  def set_session(env, sid, session, options={})
    @redis.multi do
      session.each_pair do |key, value|
        # keep string values bare so other languages can read them
        value = value.is_a?(String) ? value : Marshal.dump(value)
        @redis.hset(@session_key % sid, key, value)
      end
      @redis.expire(@session_key % sid, @expiry)
    end

    sid
  end

  def destroy_session(env, sid, option={})
    @redis.del(@session_key % sid)
    generate_sid unless options[:drop]
  end

end

You can use this from rails with:

 MyApp::Application.config.session_store MaybeMarshalRedisSession

From Rack with:

 use MaybeMarshalRedisSession

And from elsewhere with:

redis.hgetall("rack.session:#{session_id}")

If you want to call PhotoApp from your MainApp or Node.js you can make a HTTP request that includes your user's session cookie.

lastcanal
  • 2,145
  • 14
  • 17