Matt Payne Consulting

Avatar

Everything is possible

Customizing Forms Authentication in ASP.Net MVC 3

I'm in the process of building out the core functionality of ColorMatchR. One of the main ingredients is that users can log in, of course.

The obvious choice to support this requirement is Forms Authentication. This allows me to use all of the build-in security related features of ASP.NET, like the [Authorize] attribute.

Normally, this would be straightforward. In fact, the default MVC project comes with it enabled and built in by default. However, I don't really want to use the Membership API here - I'd rather use my own custom approach to authenticating the users. At the same time, though, I want it to act as if we are using simple Forms Authentication.

Thinking it through, all Forms Authentication really does is set a cookie upon successfully login. This cookie is then read per request and the built-in GenericPrincipal is rehydrated from the cookie.

So what I want to do is:
  1. Upon successful user login take over the cookie creation process and ensure a bit of additional information is placed in the cookie.
  2. For each subsequent request, rehydrate instances of my custom IPrincipal/IIdentity from the cookie and set HttpContext.User and Thread.CurrentPrincipal to my custom instances.
  3. Add some helper methods to my controllers and views so that I can access the current user.

By doing this, I can be sure that I can continue to use much of the built in security that is provided by MVC, while also having access to user-specific information that I require to satisfy the requirements of the application.

Here is the code for creating the auth cookie (this code resides in my AuthenticationService class):

 public void SetAuthCookie(string username, bool rememberMe)
 {
 var user = _dbContext.Set<User>().FirstOrDefault(u => u.Email == username);

 if(user == null)
 {
 throw new Exception("User cannot be found.");
 }

 string name = !String.IsNullOrEmpty(user.Nickname) ? user.Nickname : String.Format("{0} {1}", user.FirstName, user.LastName);
 string userData = CredentialHandler.CreateUserData(user.Id, name, user.Roles.Select(r => r.Name).ToArray());

 var ticket = new FormsAuthenticationTicket(1, username, DateTime.Now,
 DateTime.Now.AddMinutes(30), rememberMe, userData, FormsAuthentication.FormsCookiePath);

 string encryptedTicket = FormsAuthentication.Encrypt(ticket);

 HttpContext.Current.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket));
 }

The key point to note is that I set specific user data on the cookie - what that data actually is isn't really important.

Now, when the next request comes in from the authenticated user, I can simply re-construct my custom IPrincipal and IIdentity and set them for the lifetime of the request:

 public static void BindCredentials()
 {
 if (HttpContext.Current != null)
 {
 var context = HttpContext.Current;

 if (!String.IsNullOrEmpty(context.Request.FilePath) &&
 IgnoredExtensions.Any(ext => context.Request.FilePath.ToLower().EndsWith(ext.ToLower())))
 {
 _logger.Debug("Not bothering to create a user for a request to: " + context.Request.FilePath);
 return;
 }
 else
 {
 _logger.Debug("Creating a user for a request to: " + context.Request.FilePath);
 }

 var cookie = context.Request.Cookies[FormsAuthentication.FormsCookieName];

 ColorMatchRPrincipal principal = null;
 Guid? id = null;

 if (cookie != null)
 {
 var ticket = FormsAuthentication.Decrypt(cookie.Value);

 var values = ticket.UserData.Split(new[] { '^' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToList();

 id = Guid.Parse(values.First());

 string name = values.Skip(1).First();

 var roles = values.Skip(2).First().Split(new[] { '$' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToList();

 principal = new ColorMatchRPrincipal(new ColorMatchRIdentity(id, name, roles));
}
else
{
   principal = new ColorMatchRPrincipal(new ColorMatchRIdentity(id, String.Empty, new[] { "anonymous" }));
}

Thread.CurrentPrincipal = principal;
context.User = principal;
    }
 }

That's basically all there is to it. This method is defined on a helper class that I call from a registered HTTP module, but you could also call it from Authenticate_Request in Global.asax

What's great about this approach is that you can now take full advantage of the built in authentication/authorization in MVC with needing to roll your own.