The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Multi-domain single-signon

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Patrick Lenz

Posts: 168
Nickname: scoop
Registered: Apr, 2005

Patrick Lenz is the lead developer at freshmeat.net and a contributor to the typo weblog engine
Multi-domain single-signon Posted: Oct 2, 2005 4:03 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Patrick Lenz.
Original Post: Multi-domain single-signon
Feed Title: poocs.net
Feed URL: http://feeds.feedburner.com/poocsnet
Feed Description: Personal weblog about free and open source software, personal development projects and random geek buzz.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Patrick Lenz
Latest Posts From poocs.net

Advertisement

Single-signon is such a common feature request for many site networks. And while frequent at that, the powers that be impose difficulties on this task that make it a daunting experience for every web developer. In this piece, I’m outlining a practical approach for a hosted Rails application.

Revisiting the definition of “single-signon” we’ll declare that this means:

  1. A user has to be able to login with the same credentials across multiple sites
  2. For convenience, we want a user to be logged in on site B if he had logged in on site A before
  3. If a user logs out of a single site, we want him to be logged out in all other sites as well

So what is it that makes this such a tedious endeavor? Cookies.

Yes, we all knew back in 1990 that cookies were evil. But they’re differently evil at that this time.

Well, the evilness is only half the deal, because obviously, the implementation had security in mind when they decided that a cookie set by site A is only ever going to be transmitted back to site A, to something in the same domain at the very least. At that makes a ton of sense for a single site. In our case, though, it causes pain and grief.

Redirection to the rescue.

In the concept outlined here we’ll make use of a central point of cookie distribution in addition to local cookies being set by the sites themselves.

Since the collection of sites I implemented the following for serves all users from the same hosting cluster and even from the same user database I’ll skip the part of the actual synchronization of user databases or intersecting user databases as an exercise for the reader and focus on the handling of keeping a user logged in even if he bounces between sites.

First of all, we need a hook into all of our controllers since we have to make sure we recover a logged in user session from any potential entry point into our sites. This is best fitted into a before_filter method within ApplicationController.

class ApplicationController < ActionController::Base
  before_filter :sso
  def sso
    return if controller_name == "sso"

    unless session[:sso_verified] and cookies[:sso_token] and
    Token.current_token = Token.find_by_token(cookies[:sso_token])
      Token.current_token = Token.create(
        :token => new_sso_hash,
        :uri => request.request_uri,
        :portal => Portal.current_portal
      )

      redirect_to "http://#{Portal.find_by_sso(1).domain}" <<
        "/sso/verify/#{Token.current_token.token}" and return false
      end
    end
  end
end

What does this do?

First of all, we’re treating the single-signon process separate from the regular Rails sessions using a separate Token model, the code of which I’ll show in a moment.

A couple of measures ensure that a user is redirected to our central cookie site (for example login.mysite.net) upon his first hit onto our member site. That way we can check for a potentially existing user session on one of the other sites. We check for a flag within a user session session[:sso_verified], check the contents of an explicit sso_token cookie and try to fetch a token record from the database using that cookie’s contents.

If any of those fail, we create a new Token using a hashing method, redirect to the central site, specifically to its SsoController, to verify the existence of other sessions for the current browser and a little more.

In case the single-signon token is actually valid and contained in the database, you should populate the session with the logged in user record. In our application, we use User.current_user. Your mileage may vary.

The aforementioned central point of cookie distribution is setup as just another site in our network. All it’ll do, though, is act upon requests to the SsoController.

class SsoController < ApplicationController
  def verify
    Token.current_token = Token.find_by_token(params[:id])

    if cookies[:sso_token] and
    existing_token = Token.find_by_token(cookies[:sso_token])
      Token.current_token = existing_token.adopt_params(Token.current_token)
    end

    Token.current_token.cleanup

    set_sso_cookie
    redirect_to "http://#{Token.current_token.portal.domain}" <<
      "/sso/recovery/#{Token.current_token.token}"
  end
end

The verify method (now invoked on the central site) takes the passed hash value, fetches the token created in the filter from the database, checks for the presence of an sso_token cookie (again, on the central site – we would not have seen that cookie previously in the filter since we’ve been on a different domain) and fetches another token from the database in case that cookie is populated, the latter potentially containing a valid user session.

If an existing session is found, it is preferred over the newly created Token for obvious reasons. However, we’re taking the URI from the previous request to properly redirect the user back to whence he came from.

Lastly, the user is being redirected back to the originating site, again to the SsoController, to set a local cookie.

class SsoController < ApplicationController
  def recovery
    session[:sso_verified] = true
    Token.current_token = Token.find_by_token(params[:id])
    set_sso_cookie
    redirect_to Token.current_token.uri
  end  
end

In the recovery method we’re starting out by setting the verification flag in the user session. We then fetch a Token from the database using the hash passed by the central site and set the local sso_token cookie (which is, in total, cookie number 4 transmitted from our sites (a session cookie and an sso_token cookie each for the originating site and the central site)) and redirect the user back to his original entry point to the originating site.

As I mentioned, we’re not going to pollute or interfere with the regular Rails sessions. We’ll create a separate model for the authentication tokens needed for the single signon process.

class Token < ActiveRecord::Base
  belongs_to :user
  belongs_to :portal

  cattr_accessor :current_token

  def adopt_params(adoptee)
    self.uri = adoptee.uri
    self.portal = adoptee.portal
    self
  end

  def cleanup
    return if self.user.nil?
    self.class.destroy_all [ "id != ? AND user_id = ?", self.id, self.user_id ]
  end
end

This code should barely need any explanation as it’s been used by earlier snippets. The cleanup method makes sure only a single Token for a given user exists.

The open spots in this implementation mainly center around associating Token.current_token with a user account as a user logs in and remove that association upon logging out (via a controller method generated via the login generator for example).

Other spots have purposely been left out since they’re too specifically bound to my own domain model but should not be hard to fill in for the experienced reader. If you do have questions though, don’t hesitate to ask in the comments.

Read: Multi-domain single-signon

Topic: Friend says Java is out of his workplace Previous Topic   Next Topic Topic: Here is MouseHole 1.2

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use