SiteMinder is an enterprise-class secure single sign-on solution by CA (Computer Associates) which is employed by many large companies to secure their intranet access and provide single sign-on functionality to various intranet applications. SiteMinder has a broad support for different application frameworks which is making possible to use in heterogeneous enterprise environment.
For example, when SiteMinder is used to secure ASP.NET/IIS application then it’s normally configured as IIS handler. For example (in web.config):
<add name="handler-wa-32" path="*" verb="*" modules="IsapiModule" scriptProcessor="C:\Program Files\CA\webagent\win32\bin\ISAPI6WebAgent.dll" resourceType="Unspecified" requireAccess="None" preCondition="classicMode,bitness32" />
|
SiteMinder module is intercepting every request to ASP.NET application resource and authenticating and authorizing user. If user is authenticated and authorized successfully then SiteMinder is passing the request further down the pipeline to ASP.NET.
So, how too integrate SiteMinder authentication with ASP.NET MVC authentication? SiteMinder is doing a great job for handling it on it’s own, but quite often MVC application will need to doit’s own, custom authorization in order to grant or deny user access to different resources, depending on user role. |
After SiteMinder successfully identified user, it’s usually setting a set of cookies and HTTP headers, depending on it’s configuration. These cookies and HTTP headers could contain different user profile properties, like: user name, user first/last names, user role, etc. Let’s consider the minimal configuration scenario where SiteMinder is only setting a HTTP header with the user name.
ASP.NET MVC application security framework is using HttpContext.User.Identify and if it’s authenticated then user is conspired to be authenticated. So, we need first to read a user name from custom HTTP header which is set by SiteMinder, and set User.Identify. Also, it would make sense to create a custom authorization attribute to restrict user access based on user role or use a standard MVC AuthorizeAttribute to check user roles if roles could be statically assigned to application resources. User role could be either extracted from SiteMinder variable (custom HTTP header or cookie) if we want user roles to be managed in SiteMinder or loaded from application database if we want user roles to be managed in application.
The implementation of that would differ depending on the version of MVC framework. For MVC 4 and below we would need to create a handler for Application_AuthenticateRequest event in global.asax. It may look somewhat like that:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
string SiteMinderHeaderToken = “SM_USER”;
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
string userSSO = null;
if (HttpContext.Current.Request.Headers[SiteMinderHeaderToken] != null)
{
userSSO = HttpContext.Current.Request.Headers[SiteMinderHeaderToken];
Trace.WriteLine(string.Format(“got siteminder token: {0}”, userSSO));
}
if (string.IsNullOrWhiteSpace(userSSO))
{
Trace.WriteLine(“access denied, no siteminder token found”);
}
else
{
// Create GenericPrincipal with authentication type “SiteMinder”.
GenericIdentity webIdentity = new GenericIdentity(userSSO, “SiteMinder”);
// get user roles from database or from SiteMinder valiables
string[] roles = GetRoles(userSSO);
GenericPrincipal principal = new GenericPrincipal(webIdentity, roles);
HttpContext.Current.User = principal;
}
}
}
Now, you can use a standard AuthorizeAttribute on your controller actions to restrict user access based on role:
[Authorize(Roles = “Admin”)]
pubic class AdminController: Controller
{
}
However, if you using MVC 5 framework, then things are becoming even simpler. MVC 5 introduced a new type of request filters – authentication filters which are eliminating the need of handling Application_AuthenticateRequest event in global.asax. Instead we’ll need to create our custom authentication filter:
public class SiteMinderAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public string[] Roles { get; set; }
public void OnAuthentication(AuthenticationContext filterContext)
{
string SiteMinderHeaderToken = “SM_USER”;
string userSSO = null;
if (HttpContext.Current.Request.Headers[SiteMinderHeaderToken] != null)
{
userSSO = HttpContext.Current.Request.Headers[SiteMinderHeaderToken];
Trace.WriteLine(string.Format(“got siteminder token: {0}”, userSSO));
}
if (string.IsNullOrWhiteSpace(userSSO))
{
Trace.WriteLine(“access denied, no siteminder token found”);
}
else
{
// Create GenericPrincipal with authentication type “SiteMinder”.
GenericIdentity webIdentity = new GenericIdentity(userSSO, “SiteMinder”);
// get user roles from database or from SiteMinder valiables
string[] roles = GetRoles(userSSO);
GenericPrincipal principal = new GenericPrincipal(webIdentity, roles);
filterContext.HttpContext.User = principal;
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
// check your roles here
// if user cant’s access the resource then set
// filterContext.Result = new HttpUnauthorizedResult();
}
Then you just need to decorate your controller with this new custom attribute:
[SiteMinderAuthentication(Roles = “Admin”)]
pubic class AdminController: Controller
{
}
good one. The fact that you can make it extensible with MVC 5 F/W and have your custom class contain the SiteMinderHeaderToken gives enhanced flexibility based on your own specific implementation.
Also implements the decorator pattern.
Thanks Arghya!
Yes, it’s really neat that in MVC 5 it’s possible to encapsulate a whole custom authentication and authorization into a single action filter.
That’s an interesting idea. I’m trying to figure out where I should put this in a project. Where would you put that class? In a separate project that’s included into the Web app?
Thank you, Henry!
I had this code in a separate assembly, but it may as well be in web application. It’s a matter of your personal preference and the way how your project code is organized.
Yeah, I went with a separate assembly so it can be used across projects. Thanks; works well.
How we do session timeout this case (like form authentication one)