Monday, November 17, 2014

Forms Authentication Revisited for .NET 4.5

We’ve been using Claims-Based Authentication for some time now and I love it. Over two years ago I’ve blogged on how to replace Forms Authentication module with the WIF’s SessionAuthenticationModule to have a better support for authentication cookies in ASP.NET applications.

From that time, WIF has been integrated into .NET 4.5. The article needs an update to reflect changes.

A side note: I am yet not going to switch to the ASP.NET Identity 2.0 for couple of reasons. First, there are still issues and the team still works on the API and the implementation. Second, it hides simple things on so many layers of abstractions that you really need time to get things going on. Models, stores, managers, password hashers, two way authentication, OWIN, external identity providers, Entity Framework data contexts, everything is supposed to work together. And it’s great when it does for when it doesn’t, the Stack Overflow is your only hope. With all the respect to the work the Identity Team did – the library is just too complicated now to do simple things in an understandable way.

Back to the topic.

First change is the way you reference the System.IdentityModel in your web.config:

<system.webServer>
    <modules>
        <add name="SessionAuthenticationModule" 
             type="System.IdentityModel.Services.SessionAuthenticationModule, System.identitymodel.services" 
             preCondition="managedHandler"/>
    </modules>
</system.webServer>

The full name requires setting Copy Local to true but you can always provide the assembly qualified name, System.IdentityModel.Services.SessionAuthenticationModule, System.identitymodel.services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.

You also need two configuration section types

<configSections>
    <section name="system.identityModel" 
      type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel" />
    <section name="system.identityModel.services" 
      type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services" />
</configSections>

and two sections

<system.identityModel>
</system.identityModel>
 
<system.identityModel.services>
    <federationConfiguration>
        <cookieHandler requireSsl="false" />
    </federationConfiguration>
</system.identityModel.services>

The first, empty section, is required to be able to call FederatedAuthentication.SessionAuthenticationModule, a shorter way to get a reference to the Session Authentication Module. The second section is required to get a cookie that supports HTTP (and not only HTTPS).

The code to create a WIF’s cookie is then

var identity = new ClaimsIdentity( "custom" );
identity.AddClaim( new Claim( ClaimTypes.Name, txtLogin.Text ) );
 
var principal = new ClaimsPrincipal( identity );
 
SessionAuthenticationModule sam = FederatedAuthentication.SessionAuthenticationModule;
var token = 
    sam.CreateSessionSecurityToken( principal, string.Empty, 
         DateTime.Now.ToUniversalTime(), DateTime.Now.AddMinutes(20).ToUniversalTime(), false );
 
sam.WriteSessionTokenToCookie( token );

Two things to note here. First, the ClaimsIdentity constructor has a non-empty authenticationType, “custom”. Without it the identity would be treated as not authenticated.

Then, note .ToUniversalTime() after token start and expiry dates. It looks like the SessionSecurityToken constructor uses UTC internally but if non-UTC dates are passed, it just throws an exception (ArgumentOutOfRangeException, keyExpirationTime).

 

No comments: