Forms authentication and role-based security: improving performance
Note: this entry has moved.
The usual approach to custom role-based security using forms authentication
(i.e. roles and users fetched from a database) on web apps is the
following:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
// Only replace the context if it has already been handled
// by forms authentication module (user is authenticated)
if (Context.Request.IsAuthenticated)
{
string roles[];
// Fetch roles from the database somehow.
// Reuse the identity created by Forms authentication.
GenericPrincipal ppal = new GenericPrincipal(
Context.User.Identity, roles);
Context.User = ppal;
}
}
There's one *huge* drawback to this approach, and it's that it will hit the
database on every single request! So, the proposed improved solution is:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (Context.Request.IsAuthenticated)
{
// Retrieve user's identity from context user
FormsIdentity ident = (FormsIdentity) Context.User.Identity;
// Retrieve roles from the authentication ticket userdata field
string[] roles = ident.Ticket.UserData.Split('|');
// If we didn't load the roles before, go to the DB
if (roles[0].Length == 0)
{
// Fetch roles from the database somehow.
// Store roles inside the Forms ticket.
FormsAuthenticationTicket newticket = new FormsAuthenticationTicket(
ident.Ticket.Version,
ident.Ticket.Name,
ident.Ticket.IssueDate,
ident.Ticket.Expiration,
ident.Ticket.IsPersistent,
String.Join("|", roles),
ident.Ticket.CookiePath);
// Create the cookie.
HttpCookie authCookie = new HttpCookie(
FormsAuthentication.FormsCookieName,
FormsAuthentication.Encrypt(newticket));
authCookie.Path = FormsAuthentication.FormsCookiePath + "; HttpOnly; noScriptAccess";
authCookie.Secure = FormsAuthentication.RequireSSL;
if (newticket.IsPersistent)
authCookie.Expires = newticket.Expiration;
Context.Response.Cookies.Add(authCookie);
}
// Create principal and attach to user
Context.User = new System.Security.Principal.GenericPrincipal(ident, roles);
}
}
The new version only goes to the DB once. It also uses the same encryption
features of forms authentication, as well as its ticket and cookie. A huge
performance boost, that's for sure.
Thanks to Hernan
for pointing this out while reviewing the security chapter of
my last book.