I think a scenario like this with a lot of predefined conditions is not uncommon. And here the Membership and RoleProvider features of 2.0 usually don't fit (these are great features, but not always aplicable). It usually (like in our case) comes down to something like this:
Membership: Method X, Y and Z of the Membership model are not needed by the preconditions and method Q and R needs to be modified. Special methods A and B needs to be added.
RoleProvider: Preconditions requires a custom RoleProvider class with specialization requirements like with the Membership system.
One solution would be to go ahead and implement your own implementations, but you will probably end up doing a lot of (unnescessary) code work, and the intended productivity benefits will be lost.
My suggested solution is to specialize at alower level of .NET authentication and authorization - IPrincipal and IIdentity. The steps would be:
- Make your own implementation of IIdentity if needed (usually GenericIdentity or FormsIdentity are sufficient)
- Make your own implementation of IPrincipal. Example:
public class MyPrincipal : IPrincipal { public MyPrincipal(IIdentity ident, List<string> roles, int someCustomProperty1, string someCustomProperty2) { this.identity = ident; this.roles = roles; this.someCustomProperty1 = someCustomProperty1; this.someCustomProperty2 = someCustomProperty2; } IIdentity identity; public IIdentity Identity { get { return identity; } } private List<string> roles; public bool IsInRole(string role) { return roles.Contains(role); } private int someCustomProperty1; public int SomeCustomProperty1 { get { return someCustomProperty1; } } private string someCustomProperty2; public string SomeCustomProperty2 { get { return someCustomProperty2; } } }
Set up web.config to forms based authentication. Typically like:
<system.web> <authentication mode="Forms"> <forms loginUrl="Logon.aspx"> </forms> </authentication> <authorization> <deny users="?" /> </authorization> </system.web>
- Your code: A succesful login should establish the encrypted cookie:
- Your code: Global.asax should enrich each request with the needed extra data and cache it:
protected void Application_AuthenticateRequest(object sender, EventArgs e) { if (HttpContext.Current.User != null) { if (HttpContext.Current.User.Identity.IsAuthenticated) { if (HttpContext.Current.User.Identity is FormsIdentity) { // Get Forms Identity From Current User FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity; // Create a custom Principal Instance and assign to Current User (with caching) MyPrincipal principal = (MyPrincipal)HttpContext.Current.Cache.Get(id.Name); if (principal == null) { // Create and populate your Principal object with the needed data and Roles. principal = MyBusinessLayerSecurityClass.CreatePrincipal(id, id.Name); HttpContext.Current.Cache.Add( id.Name, principal, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(0, 30, 0), System.Web.Caching.CacheItemPriority.Default, null); } HttpContext.Current.User = principal; } } } }
Looking from an architechtural view, our specializations will reside in 3 places:
- IIdentity: Your implementation must contain some sort of unique user identity - nothing else.
- IPrincipal: Your implementation can contain extra user information, and role checking logic must be present (IsInRole as a minimum).
- Your Business logic: Here you should place the code that handles your specific security - like checking a login, getting the users roles, etc., as well as all the very-special-method-x methods that you preconditions require.
Clever construction, Peter!
ReplyDeleteGood work.
Nice example. However there is a small error in your principal. The field someCustomProperty should have been someCustomProperty1.
ReplyDeleteIs there an easy way to make each of these properties available to every single view rendered by WebForms, or say, Razor templates in ASP.NET?
ReplyDeleteWhat is MyBusinessLayerSecurityClass???
ReplyDeleteI try to implement this Principal in my asp.net MVC 2 application
@bkreeger
ReplyDeleteUsing the example in the post, you will always be able to access (MyPrincipal)HttpContext.Current.User, or in MVC more prefferably (MyPrincipal) ControllerContext.HttpContext.User.
@stefan
It's your custom class that creates your custom principal with, say, some application-specific properties, associations to related domain model objects, etc.
hi @peter, i got an question. This info that you put in cache is really necessary? I can't see where do you can use it.
ReplyDeletethx
okay, now i understand. The Application_AuthenticateRequest executes more than once.
ReplyDeleteHello Peter,
ReplyDeleteThanks for this very well explained post. I'm trying to get this to work with a MVC3 application but the httpcontext.current.user gets reset to the default System.Web.Security.RolePrincipal inmediately when I hit the controller so it fails with an unable to cast exception. I checked that the code in the global.asax is run but it still fails. I get it to work by placing the code from the application_authenticaterequest in the constructor of the controller. While it works doesn't seem like an ideal solution, plus I have several controllers. Any idea what I may be missing? thanks a lot.