Skip navigation.

Survival of the UnfittestAll recent postsSorting DataView on Non-existent Columns

Authentication Ticket Hurdle

When implementing forms authentication in a web app I get my hands dirty with the FormsAuthenticationTicket class as opposed to the traditional RedirectFromLoginPage. I feel that tweaking an authentication ticket gives me a lot more flexibility. Besides, it's pretty much the only way to attach, say, a list of user's roles to a ticket.

The other day I ran into an issue with an authentication cookie that took me about 20 minutes to sort out. To follow along, please see How To Implement IPrincipal for a great example of how to issue an authentication ticket.

The problem lies with an overloaded constructor which allows you to pass user-defined data (the last parameter in this example):

// Create the authentication ticket
 FormsAuthenticationTicket authTicket = new 
         FormsAuthenticationTicket(1, // version
         txtUserName.Text, // user name
         DateTime.Now, // creation
         DateTime.Now.AddMinutes(60),// Expiration
         false, // Persistent
         roles); 

If you choose to supply no user-defined data and pass a null is in the last parameter you simply get bounced to the login page. It happens no matter what. No error, no exception.

To correct the problem pass anything. String.Empty will do. Weird.

Roles In User-Defined Data

Whitepapers and articles I've seen this far show you how to package user's roles along with an authentication ticket. Refer to How To Implement IPrincipal again:

private string GetRoles( string username, string password )
{
 // Lookup code omitted for clarity
 // This code would typically look up the role list from
 // a database table.
 // If the user was being authenticated against Active Directory,
 // the Security groups and/or distribution lists that the user
 // belongs to may be used instead.
 return "Senior Manager|Manager|Employee";
}

Later on this list of roles is passed to the ticket constructor as user-defined data:

string roles = GetRoles (txtUserName.Text, txtPassword.Text);

FormsAuthenticationTicket authTicket = new 
    FormsAuthenticationTicket (..., roles ); // User data

The list of roles travels with the authentication cookie from this point on!

It works if the role membership remains more or less static. If a user's membership is subject to change at any time don't do this because the principal object will reflect the role list from the cookie, not your database or Active Directory. This is a very important point.

// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];

FormsAuthenticationTicket authTicket = 
      FormsAuthentication.Decrypt (authCookie.Value);

// When the ticket was created, the UserData property was assigned a
// pipe delimited string of role names.
string[] roles = authTicket.UserData.Split ('|');

// Create an Identity object
FormsIdentity id = new FormsIdentity (authTicket); 

// This principal will flow throughout the request.
CustomPrincipal principal = new CustomPrincipal (id, roles);

// Attach the new principal object to the current HttpContext object
Context.User = principal;

You see from this example that the role list is unpacked from the authentication cookie and used to build a principal. The role list doesn't reflect membership changes you've made since the cookie was issued last, which in the case of a permanent cookie is forever.

My Workaround

I took me another half-hour to figure this out. From now on I pass String.Empty as user-defined data when issuing a ticket. When an authentication request is made, I create a new principal and pass it a list of user's roles I read from the database. At this point it's safe to call IPrincipal.IsInRole.

If anyone knows of a more efficient way to keep membership info in a cookie fresh, please leave a comment.

Comments

Comment permalink 1 Bill |
Dude, your choice of green in the code block is somewhat unreadable...good points though.
Comment permalink 2 Milan Negovan |
Point taken. ;) I made the comments a lil' darker. How about now?
Comment permalink 3 Brant LeClercq |
It is probably best to store the roles in cache (loaded from db after authentication of course), then you can programmatically change the roles while the user is logged in. Then just persist the changes to db either when the change is made or when the roles list is removed from cache.
Comment permalink 4 Milan Negovan |
You're reading my mind. Setting a database cache dependency on a role list is what I'm going to write about in a day or two. :)
Comment permalink 5 Brant LeClercq |
Sorry, I should clarify. I meant load the roles into cache after authentication if they are not already in there. Then load the roles from the cache into your CustomPrincipal.

But your workaround is pretty much the only way to get around the problem of the auth ticket potentially not changing. Or in other cases where the cookie doesn't have enough space to store all the roles.

Emails and Notifications

Would you like to be notified when somebody responds to this post?  Would you like to have these comments emailed to you?

TrackBacks

Sorry, TrackBacks are not allowed.

Submit your comment

Please enter only text since all HTML tags except hyperlinks will be stripped. Hyperlinks will become live links. Any comments with flaming or offensive language will be deleted. Be courteous to other posters. Thank you.

Your name (required):
Your email (optional):
Your site's URL (optional):
Enter this number
Type in the number above:
Comment (required):