Single Sign-On Across Multiple Domains
I'm trying to develop an easy to use HttpModule for enabling Single Sign-On across multiple domains. My idea was to have a single login page on one domain and then a HttpModule that checked for an authentication cookie on each domain and if there wasn't one already enabled it would redirect to the single login site and check if you were authenticated there. If you were the site would then forward you back with a query string so that you can create the cookie on that side.
Heres the code i've attempted:
HTTP MODULE
using System;
using System.Web;
using System.Web.Security;
using System.Configuration;
namespace SSOLibrary
{
/// <summary>
/// Summary description for SSOHttpModule.
/// </summary>
public class SSOHttpModule : IHttpModule
{
public SSOHttpModule() { /* nothing to construct */ }
#region IHttpModule Members
public void Init(HttpApplication context)
{
// subscribe to the begin request event
context.BeginRequest += new EventHandler(context_BeginRequest);
}
public void Dispose() { /* Nothing to dispose of */ }
#endregion
private void context_BeginRequest(object sender, EventArgs e)
{
string authDomain = ConfigurationSettings.AppSettings["authDomain"].ToString() + "?returnUrl=" + HttpContext.Current.Request.Url;
// see if the user is logged in
HttpCookie c = HttpContext.Current.Request.Cookies[ConfigurationSettings.AppSettings["cookieName"]];
if (c != null && c.HasKeys) // the cookie exists!
{
string cookie = HttpContext.Current.Server.UrlDecode(c.Value);
FormsAuthenticationTicket fat = FormsAuthentication.Decrypt(cookie);
return; // cookie decrypts successfully, continue processing the page
}
// the authentication cookie doesn't exist - ask AuthDomain if the user is logged in there
if(HttpContext.Current.Request.UrlReferrer == null)
HttpContext.Current.Response.Redirect(authDomain);
UriBuilder uri = new UriBuilder(HttpContext.Current.Request.UrlReferrer);
UriBuilder safeUri = new UriBuilder(ConfigurationSettings.AppSettings["authDomain"]);
if (uri.Host != safeUri.Host || uri.Path != safeUri.Path) // prevent infinite loop
{
HttpContext.Current.Response.Redirect(authDomain);
}
else
{
// we are here because the request we are processing is actually a response from AuthDomain
if (HttpContext.Current.Request.QueryString["ssoauth"] == null)
{
// AuthDomain does not have the authentication cookie
return; // continue normally, this user is not logged-in
}
else
{
// user is logged in to AuthDomain and we got his name!
string userName = (string)HttpContext.Current.Request.QueryString["ssoauth"];
// let's create a cookie with the same name
FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddHours(1), true, "");
HttpCookie cookie = new HttpCookie(ConfigurationSettings.AppSettings["cookieName"]);
cookie.Value = FormsAuthentication.Encrypt(fat);
cookie.Expires = fat.Expiration;
HttpContext.Current.Response.Cookies.Add(cookie);
}
}
}
}
}
--
AUTH DOMAIN
--
private void Page_Load(object sender, System.EventArgs e)
{
// This is our caller, need to redirect back to it
UriBuilder uri = new UriBuilder(Request.UrlReferrer);
HttpCookie c = HttpContext.Current.Request.Cookies[ConfigurationSettings.AppSettings["cookieName"]];
if (c != null && c.HasKeys) // the cookie exists!
{
string cookie = HttpContext.Current.Server.UrlDecode(c.Value);
FormsAuthenticationTicket fat = FormsAuthentication.Decrypt(cookie);
uri.Query = uri.Query + "&ssoauth=" + fat.Name; // add logged-in user name to the query
}
Response.Redirect(uri.ToString()); // redirect back to the caller
}
The problem is that URLReferer is always null. Does anyone have a better way of doing this?
[4062 byte] By [
sontek] at [2008-2-18]
The reason the URL referrer was null was because of security settings
on my browser. So using URLReferrer was not a realiable way of tracking
which domain sent the request so I decided to use a returnURL query
string. I also decided instead of returning the username in a query
string that I would instead build a token system. When the user logs
in it will generate a token for them and insert it into the database
with an expiration time and then the SingleSignOn.aspx page will send
the token to the query string and the Domain site needing
authentication will build the cookie by the token.
Heres the code:
HTTP MODULE
using System;
using System.Web;
using System.Web.Security;
using System.Configuration;
namespace SSOLibrary
{
/// <summary>
/// Summary description for SSOHttpModule.
/// </summary>
public class SSOHttpModule : IHttpModule
{
public SSOHttpModule() { /* nothing to construct */ }
#region IHttpModule Members
public void Init(HttpApplication context)
{
// subscribe to the begin request event
context.BeginRequest += new EventHandler(context_BeginRequest);
}
public void Dispose() { /* Nothing to dispose of */ }
#endregion
private void context_BeginRequest(object sender, EventArgs e)
{
string authDomain =
ConfigurationSettings.AppSettings["authDomain"].ToString() +
"?returnUrl=" + HttpContext.Current.Request.Url;
// see if the user is logged in
HttpCookie c = HttpContext.Current.Request.Cookies[ConfigurationSettings.AppSettings["cookieName"]];
if (c != null) // the cookie exists!
{
string tempCookie = HttpContext.Current.Server.UrlDecode(c.Value);
FormsAuthenticationTicket tempFat = FormsAuthentication.Decrypt(tempCookie);
// cookie decrypts successfully, continue processing the page
return;
}
// the authentication cookie doesn't exist - ask AuthDomain if the user is logged in there
if(HttpContext.Current.Request.QueryString["token"] == null)
HttpContext.Current.Response.Redirect(authDomain);
// user is logged in to AuthDomain and we got his name!
string token = (string)HttpContext.Current.Request.QueryString["token"];
if(token != String.Empty)
{
string userName = TokenFacade.ValidateToken(token);
if(userName != String.Empty)
{
// let's create a cookie with the same name
FormsAuthenticationTicket fat = new
FormsAuthenticationTicket(1, userName, DateTime.Now,
DateTime.Now.AddHours(1), true, "");
HttpCookie cookie = new HttpCookie(ConfigurationSettings.AppSettings["cookieName"]);
cookie.Value = FormsAuthentication.Encrypt(fat);
cookie.Expires = fat.Expiration;
HttpContext.Current.Response.Cookies.Add(cookie);
}
}
}
}
}
-
AUTH DOMAIN
private void Page_Load(object sender, System.EventArgs e)
{
string query = "token=";
// This is our caller, need to redirect back to it
if(Request.QueryString["returnURL"] != null)
{
UriBuilder uri = new UriBuilder(Request.QueryString["returnURL"]);
HttpCookie c = HttpContext.Current.Request.Cookies[ConfigurationSettings.AppSettings["cookieName"]];
if (c != null) // the cookie exists!
{
string cookie = HttpContext.Current.Server.UrlDecode(c.Value);
FormsAuthenticationTicket fat = FormsAuthentication.Decrypt(cookie);
string token = SSOLibrary.TokenFacade.GetTokenByUserName(fat.Name);
query += token;
}
uri.Query = query;
Response.Redirect(uri.ToString()); // redirect back to the caller
}
}