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.

Wednesday, September 5, 2012

Forms Authentication revisited

Forms authentication is an authentication module, known for years and quite reliable. The core idea is that the authentication pipeline takes the so called forms authentication cookie, decrypts the cookie and sets the HttpContext.Current.User for a request. Forms authentication subsystem contains an API which can issue cookies, in particular you can attach a small portion of custom data to the cookie so that the data is available as long as the user is logged in.

Issues

There are two common issues around the Forms authentication module: the custom data cannot be too long and because it is a string – there is no natural way to make it “compound” (contain a structure of few fields).

The second issue can possibly be solved by creating your own structure (XML for example), serializing and deserializing the data. Nothing out of the box.

However, I haven’t found any solution to the the first issue. Let’s face it:

string ReallyLongUserData
{
    get
    {
        Random r = new Random();
 
        StringBuilder sb = new StringBuilder();
 
        while ( sb.Length < 8192 )
            sb.Append( r.Next().ToString() );
 
        return sb.ToString();
    }
}
 
...
 
// create a forms cookie with really long userdata (>8192)
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
    1, txtUserName.Text, DateTime.Now, 
    DateTime.Now.AddMinutes( 20 ), false, ReallyLongUserData );
 
HttpCookie cookie = new HttpCookie( FormsAuthentication.FormsCookieName );
cookie.Value = FormsAuthentication.Encrypt( ticket );
this.Response.AppendCookie( cookie );
 
Response.Redirect( this.Context.Request.QueryString["ReturnUrl"] );

Guess what happens if you run this.

The answer is: the cookie is signed and encrypted and appended to the response. But apparently, the cookie size clearly exceedes the maximum size of the cookie browsers can handle. Most browsers ignore the cookie then! The cookie is missing from subsequent requests.

SessionAuthenticationModule for the rescue!

If there’s a way to replace the FormsModule with a custom module capable of handling multiple cookies, then the cookie size issue would just be gone. Let the module just split the long value to multiple cookies, TheCookie1, TheCookie2, … and then, at the server, join the cookie data and recreate the identity. And what if the module supports multiple userdata entries, a dictionary which maps keys to values possibly?

Sounds great?

Well, there IS such module. It is called SessionAuthenticationModule.

It is part of the Windows Identity Foundation and normally it is used in federation scenarios where identity cookie is issued according to SAML tokens from identity providers (here it doesn’t really matter what a SAML token is). If you work with WIF, you know the module.

However, the SessionAuthenticationModule can be used in a normal application, just to replace the incapable Forms module and provide additional features.

First, the configuration:

<configuration>
 
    <configSections>
        <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </configSections>
 
    <system.web>
 
        <authentication mode="Forms">
            <forms loginUrl="LoginPage.aspx" />
        </authentication>
        
        <authorization>
            <deny users="?"/>
            <allow users="*"/>
        </authorization>
 
        <httpModules>
            <add name="SessionAuthenticationModule" 
                 type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, 
                       Microsoft.IdentityModel, Version=3.5.0.0, 
                       Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        </httpModules>
 
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
 
    <system.webServer>
        <validation validateIntegratedModeConfiguration="false" />
        <modules>
            <add name="SessionAuthenticationModule" 
                 type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, 
                       Microsoft.IdentityModel, Version=3.5.0.0, 
                       Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
        </modules>
    </system.webServer>
 
    <microsoft.identityModel>
        <service>
            <federatedAuthentication>
                <cookieHandler name="ADWebFrontEndCookie" requireSsl="false" />
            </federatedAuthentication>
        </service>
    </microsoft.identityModel>
 
</configuration>

Note that I declare that I am using Forms module. This is to utilize the Forms ability to authorize Url access and redirect the browser to the login page in case of insufficient priviledges. The SAM module’s cookie name is also declared in the module’s section.

And there comes the code:

string ReallyLongUserData
 {
     get
     {
         Random r = new Random();
 
         StringBuilder sb = new StringBuilder();
 
         while ( sb.Length < 8192 )
         //while ( sb.Length < 512 )
             sb.Append( r.Next().ToString() );
 
         return sb.ToString();
     }
 }
 
...
SessionAuthenticationModule sam = 
   (SessionAuthenticationModule)
   this.Context.ApplicationInstance.Modules["SessionAuthenticationModule"];
 
IClaimsPrincipal principal = 
   new ClaimsPrincipal( new GenericPrincipal( new GenericIdentity( txtUserName.Text ), null ) );
 
// create any userdata you want. by creating custom types of claims you can have
// an arbitrary number of your own types of custom data
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Email, "foo@bar.com" ) );
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.UserData, ReallyLongUserData ) );
 
var token = 
    sam.CreateSessionSecurityToken( 
        principal, null, DateTime.Now, DateTime.Now.AddMinutes( 20 ), false );
sam.WriteSessionTokenToCookie( token );
 
Response.Redirect( this.Context.Request.QueryString["ReturnUrl"] );
     

This code replaces the previous code – instead of issuing a Forms cookie, now I am issuing a SAM cookie.

And …. that’s it. The cookie is created, it is automatically split into smaller cookies so that the browser’s limit doesn’t break anything. Also, now I can easily create any type of custom data I want by mapping claim types to values. Note that multiple claims of the same type can be assigned to the identity.

Authorization with the SAM module

Another surprizing benefit of using the SAM module is that it simplifies role management and authorization. Upon each request, the module recreates the identity which is of type ClaimsPrincipal. This class has the IsInRole method already implemented. And the way you feed it with roles is straightforward:

IClaimsPrincipal principal = 
   new ClaimsPrincipal( 
      new GenericPrincipal( new GenericIdentity( txtUserName.Text ), null ) );
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Email, "foo@bar.com" ) );
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.UserData, ReallyLongUserData ) );
 
// roles, stored in the cookie as claims!
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Role, "ADMIN" ) );

It turns out then that you don’t really have to change much in your application - the authorization should work, including the static authorization with authorization sections of web.config files. What you only need to provide is to add role claims when you create the principal object.

Happy coding.

Indexing attributes in the Active Directory

The default Microsoft Active Directory schema is full of attributes. If you by chance pick some unused attributes and reuse them for your own purposes, there’s a caveat – some attributes are indexed and some other are not. And as you can expect – this makes a huge difference if your business processing relies on searching the AD database.

Fortunately, AD can be reconfigured to create an index over any attribute. These articles describe all required steps:

http://technet.microsoft.com/en-us/library/cc755885(WS.10).aspx

http://technet.microsoft.com/en-us/library/aa995762(EXCHG.65).aspx

In short:

  1. You have to be in “Schema Admins” AD group
  2. You have to install the Active Directory Schema MMC snapin (just regsvr32.exe schmmgmt.dll and the snapin will be available)
  3. Run the snapin “as the administrator” from the shell
  4. Locate your attribute, double click it and check both “Index this attribute” and (optionally) “Replicate this attribute to the Global Catalog”

Wednesday, June 27, 2012

Using SysInternals ProcessExplorer to find the cause of “access denied” error

Often when you try to rebuild your project or even just delete or rename a file, you get the “access denied” error. For example, I run my application from the command line and while it runs I try to rebuild it in Visual Studio:

Such “access denied” issues are also caused by system services like for example name-it-source-control caching service. You just can’t delete your files becase a service locks them.

One of the simplest ways to locate the true culprit – the application or the service which locks the access to the specified file/files – is to use the ProcessExplorer from SysInternals.

In PE, just navigate to Find/Find Handle or DLL. ProcessExplorer will find out and show the list of processes which have active handles to your file:

The example demonstrates the situation described at the beginning – the cause of the access denied to ConsoleApplication55.exe was the ConsoleApplication55.exe process running in the operation system. However, you can expect other valuable findings, like for example the TSVNCache.exe.

Monday, June 18, 2012

IIS 7.5 Integrated Security with no prompt for credentials

If you try to configure an IIS site to use integrated security and still get the prompt for credentials, here are few key things to validate.

First, make sure that Anonymous Authentication is turned OFF for the site:

Second, enable integrated security in Interner Explorer (Options/Advanced and checkin the “Enable Integrated Windows Authentication” option).

Third, add your website to Local Intranet zone and select at least “Automatic logon only in Intranet Zone” option under Options/Security Settings/Local intranet/Custom level).

Fourth, make sure the user and application server are in the same domain.

Wednesday, June 13, 2012

Facebook OAuth2 adventures

OAuth2 is a simple and reliable SSO protocol and what’s great is that it’s implemented by at least few well-known services, like Google, Microsoft or Facebook. I’ve decided to follow the specification and implement SSO gateways for these three.

Google – no issues, works like a charm.

Microsoft – the same.

Facebook – since don’t use Facebook, I’ve expected to get into issues. First, to register the application in Facebook you need their account. So I create one, something like “Test Developer” with one of my emails. I confirm the email by clicking their link and I am there, Facebook welcome.

So I go to the developer section and try to register the application. Facebook says that “there is a chance that this is not a legitimate account, you need to provide the phone number so that we can SMS you and verify the account. Note that the phone number will not be available for registration with other Facebook accounts”. Well, I am not going to spent one of my phone numbers on test account. I give up and decide to register a new, legitimate account.

Once again, I create an account, this time with all valid information. I confirm my email address and again I go to the developer section. The same story, “this is probably not a legitimate account […]” so I enter my phone number.

Great, they send me the SMS message, I input the number from text message and submit the page.

“We still don’t believe that this is a legitimate account. To prove it, please scan your personal ID card of your country so that first name, last name and birth date are clearly visible and submit the image here”.

This is enough for me. I am not going that far, I will ask someone who uses Facebook to assist me with the app registration. But I don’t like the privacy policy and I am going to delete the account.

Well, I find the option to delete the account, I answer the question and my account is deleted.

Is it?

I get an email which says that “thank you, your account has been locked, if you ever decide to come back, please login again with your username and password”.

Great.

Friday, June 1, 2012

How to implement XPO’s Soft Delete pattern for NHibernate

Soft Delete is a persistence pattern where instead of removing entities physically you just mark them as deleted by setting a fixed value in one of columns.

For example, in XPO (DevExpress Persistence Objects) an additional int column called GCRecord holds a null value (not deleted) or anything than null (deleted).

There’s a long discussion whether or not such approach is a pattern or an antipattern but this is beyond this post. What I would like to show is how to get soft deletes with NHibernate so that you can migrate your XPO application to NHibernate and keep your database untouched.

Filtering the data

There are two issues to solve. First – NHibernate should automatically append “filters” so that all queries will select only the data which is not deleted. I’ve blogged on this few months ago. An example code which sets filters for XPO-compatible database is:

// add this for fluent configuration
config.AddFilterDefinition( 
   new NHibernate.Engine.FilterDefinition( 
       "GCRecordFilter", null, new Dictionary<string, IType>(), true ) );
 
// create filters programatically
foreach ( var mapping in config.ClassMappings )
{
    mapping.AddFilter( "GCRecordFilter", "GCRecord is null" );
    foreach ( var property in mapping.PropertyIterator )
        if ( property.Value is Bag )
        {
            Bag bagProperty = (Bag)property.Value;
 
            bagProperty.AddFilter( "GCRecordFilter", "GCRecord is null" );
        }
}

and then you just enable filters:

_session.EnableFilter( "GCRecordFilter" );

Because of filters, NHibernate will append the “GCRecrod is null” to queries and subqueries thus simulating the way XPO filters the data.

Note that NHibernate’s feature of filtering is way, way more advanced – you are free to define any filter you need while XPO has one builtin filter which cannot be easily changed (can it be changed at all?).

Persisting the data

The remaining task is to teach NHibernate to mark records as deleted when it comes to deleting data. This is done with an event listener:

public class SoftDeleteEventListener : DefaultDeleteEventListener
{
    protected override void DeleteEntity( IEventSource session, object entity,
        EntityEntry entityEntry, bool isCascadeDeleteEnabled,
        IEntityPersister persister, ISet transientEntities )
    {
        // be smart - if the entity implements a marking interface - softdelete it
        if ( entity is ISoftDeletable )
        {
            var e = (ISoftDeletable)entity;
            e.GCRecord = 1;
 
            CascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities );
            CascadeAfterDelete( session, persister, entity, transientEntities );
        }
        // else delete it
        else
        {
            base.DeleteEntity( session, entity, entityEntry, isCascadeDeleteEnabled,
                              persister, transientEntities );
        }
    }
}

which is applied by:

config.SetListener( NHibernate.Event.ListenerType.Delete, new SoftDeleteEventListener() );

Audit logging the data

Event listeners can be used not only to soft delete the data. Suppose for example that your entity holds a simple auditing info (insert date, modify date):

public interface ISoftDeletable
{
    int? GCRecord { get; set; }
    DateTime? ModifyDate { get; set; }
    DateTime? InsertDate { get; set; }
}

You can set up an event lister to automatically set values to the two columns:

public class CustomSaveEventListener : DefaultSaveEventListener
{
    protected override object PerformSaveOrUpdate( SaveOrUpdateEvent evt )
    {
        ISoftDeletable entity = evt.Entity as ISoftDeletable;
        if ( entity != null )
            ProcessEntityBeforeInsert( entity );
 
        return base.PerformSaveOrUpdate( evt );
    }
 
    internal virtual void ProcessEntityBeforeInsert( ISoftDeletable entity )
    {
        if ( entity.InsertDate == null )
            entity.InsertDate = DateTime.Now;
        entity.ModifyDate = DateTime.Now;
    }
}
 
public class CustomUpdateEventListener : DefaultUpdateEventListener
{
    protected override object PerformSaveOrUpdate( SaveOrUpdateEvent evt )
    {
        ISoftDeletable entity = evt.Entity as ISoftDeletable;
        if ( entity != null )
            ProcessEntityBeforeInsert( entity );
 
        return base.PerformSaveOrUpdate( evt );
    }
 
    internal virtual void ProcessEntityBeforeInsert( ISoftDeletable entity )
    {
        if ( entity.InsertDate == null )
            entity.InsertDate = DateTime.Now;
        entity.ModifyDate = DateTime.Now;
    }
}

and apply listeners:

config.SetListener( NHibernate.Event.ListenerType.Save, new CustomSaveEventListener() );
config.SetListener( NHibernate.Event.ListenerType.Update, new CustomUpdateEventListener() );

Putting all this toghether, it is possible to migrate your XPO persistence layer to NHibernate without losing the Soft Delete feature.