I've been using and teaching the ASP.NET Provider Model for some time now so I always felt quite well versed on the topic, specially in the context of membership and security. I thought I understood the "order of operation" in ASP.NET membership pretty well so every time I found the need to write a custom membership provider, I approached the task with confidence.
However, I have noticed that everytime I needed to write a custom membership provider, it has been for reasons of extending Microsoft's two-level (users and role) logical model with a more complex physical one. For example, on several ocassions I've had customers who needed not only users and roles, but also a third level like groups. Since this particular need came up on more than one ocassion, I actually wrote a complete provider kit with a custom membership, role, and a new group provider; complete with custom controls and everything - quite proud of it actually.
Because I needed to store more than just user and roles (groups), I've always resorted to writing a custom principal and identity object that contains the user's credential model. And since I've needed a custom principal, I've also needed the ability to repopulate it on every request and place it into the HttpContext's User property and the Thread's current principal property.
This is something that Microsoft's FormsAuthenticationModule does for you, but it does it from its own store and into its own principal and identity objects. My needs for a more complex model always prohibited me from using the Microsoft stuff... until now.
So believe it or not, after being so close to this topic, I now have need that on the surface appears significantly simpler. I need a simple user and role model, with no added complexity, but stored in a custom database, not the "aspnet_" tables that Microsoft provides. So I need to write a custom membership provider again; easy enough.
But when I got the part where I thought I would need a custom principal and identity, and thus a custom module used to repopulate the information on each request, I thought there must be an easier way.
I'm not extending the logical two-level security model so why can't I use whatever principal and identity Microsoft is using as well as Microsoft's repopulation module (FormsAuthenticationModule). After searching quite a bit on how to do this, I come across several articles on writing custom membership providers; something I know how to do already. But none show you how to take it further.
That brings us to the present. The answer on how to do this is quite simple, yet quite undocumented.
The answer is not only in a membership provider, but in a role provider as well.
When you implement the ValidateUser method of the custom membership provider, you need only return a true or false, end of story. This will automatically create a principal and identity object (FormsIdentity) and persist the user name there. It will also place this Principal in HttpContext.Current.User. The FormsAuthenticationModule is designed to repopulate this bucket from a cookie that is managed for you. This part I knew, but since I always needed to read in a couple of authorization levels at this point, I've needed my own storage and repopulation mechanism.
public override bool ValidateUser(string username, string password)
Person person = SecurityEngine.AuthenticateUser(username, password);
return (person != null);
When all you need is roles, you simply create a custom role provider and implement the GetRolesForUser method. Here you obtain a list of roles based on a user name (passed in for you) and return it as a string array. It's as simple as that. The RolePrincipal and FormsIdentity objects are created and populated for you. This method is hit every time either you or ASP.NET performs a role-check.
public override string GetRolesForUser(string username)
Person person = SecurityEngine.FetchPerson(username);
List<string> roles = new List<string>();
foreach(Role role in person.Roles)
If this is the "live" effect that you seek, you're done. If you want the role list to be stored in the authentication cookie and repopulated by the FormsAuthenticationModule, you just need to set an attribute to "true" when you install you role provider in the config file.
<add name="customProvider" type="MyProject.Web.Security.MyMembershipProvider" />
</membership>roleManager enabled="true" defaultProvider="customProvider" cacheRolesInCookie="true">
<add name="customProvider" type="MyProject.Web.Security.MyRoleProvider" />
And that's it. This role provider detail doesn't seem to a part of any article out there. If I'm wrong, I welcome feedback.