Thursday, September 6, 2012

SessionAuthenticationModule and dynamic authorization

In my last blog post I’ve described a method to replace the FormsAuthenticationModule with the SessionAuthenticationModule. As it turned out, the SessionAuthenticationModule has some advantages over the forms module. Please refer to the article for more details.

Specifically, the last part of the article is about authorization. What I said is that you can create your roles as claims and the module will serialize roles into the authentication cookie. Then, upon each request the user roles are retrieved from the cookie.

Forms authentication have a clear distinction between static and dynamic role management scenarios - each role provider has the CacheRolesInCookie property (http://msdn.microsoft.com/en-us/library/system.web.security.roles.cacherolesincookie.aspx) which switches between static and dynamic management: when the property is false, the role provider fires at each request, when it is true, roles are cached in a cookie.

Note that from the two scenarios, the latter (storing roles in a cookie) is a direct counterpart of our SessionAuthenticationManager scenario, where roles (claims of type Role) were cached in a cookie.

But what if you want to have your roles assigned dynamically, upon each request? Not only just when you authenticate a user for the first time?

The answer is – create your own claims authentication manager. In theory, the authentication manager fires at each request in the processing pipeline and allows you to inject custom claims to existing identity. I’ve blogged about it some time ago.

The practice is, unfortunately, slightly different. In fact, the authentication manager does not fire at each request! This is because the implementation of the SessionAuthenticationModule contains a strong condition:

// SAM class
public class SessionAuthenticationModule : HttpModuleBase
{   
  protected virtual void OnPostAuthenticateRequest(object sender, EventArgs e)
  {
    if (!(HttpContext.Current.User is IClaimsPrincipal))
    {
      IClaimsPrincipal claimsPrincipal = ClaimsPrincipal.CreateFromHttpContext(HttpContext.Current);
      ClaimsAuthenticationManager claimsAuthenticationManager = base.ServiceConfiguration.ClaimsAuthenticationManager;
      if (claimsAuthenticationManager != null && claimsPrincipal != null && claimsPrincipal.Identity != null)
      {
        claimsPrincipal = claimsAuthenticationManager.Authenticate(HttpContext.Current.Request.Url.AbsoluteUri, claimsPrincipal);
      }
      HttpContext.Current.User = claimsPrincipal;
      Thread.CurrentPrincipal = claimsPrincipal;
    }
  }
}

You see the condition in the provided implementation? If the current user is not an claims principal then fire the authentication manager.

When does it happen? There are two possible scenarios:

  • the user is not yet authenticated
  • the user is authenticated by another authentication module (like the forms authentication module)

The third scenario: the user is authenticated by the SessionAuthenticationModule is not handled by this condition! The claims authentication manager will not run.

To fix this and have the manager executed even when a user is authenticated and the identity comes from the SAM, I just have to provide a counterpart implementation:

public class Global : System.Web.HttpApplication
{
    void Application_PostAuthenticateRequest( object sender, EventArgs e )
    {
        // handle the missing scenario
        if ( HttpContext.Current.User is IClaimsPrincipal )
        {
            IClaimsPrincipal claimsPrincipal = HttpContext.Current.User as IClaimsPrincipal;
            ClaimsAuthenticationManager claimsAuthenticationManager = 
                FederatedAuthentication.ServiceConfiguration.ClaimsAuthenticationManager;
            if ( claimsAuthenticationManager != null && 
                 claimsPrincipal != null && claimsPrincipal.Identity != null )
            {
                // and execute the manager
                claimsAuthenticationManager.Authenticate( HttpContext.Current.Request.Url.AbsoluteUri, claimsPrincipal );
            }
            //HttpContext.Current.User = claimsPrincipal;
            //Thread.CurrentPrincipal = claimsPrincipal;
        }
    }

As you can see, this was added to the global application class and is merely a copy-paste from the SAM’s implementation but what I change is the condition – the condition is complementary to the one from SAM. The authentication manager is always executed then – first two described scenarios are handled by SAM, the last one – by this snippet above.

Ultimately, the claims authentication manager can be used now to manage roles dynamically as role claims can be dynamically injected in each separate request in the processing pipeline.

No comments: