ASP.NET 2.0 Forms authentication - Keeping it customized yet simple

by Peter Ravnholt 24. November 2007 23:02

In my continuous quest to migrate a large ASP site to ASP.NET, one central step was to implement forms authentication that comformed to all the existing schemas and business logic around users and user rights.

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:

 

FormsAuthentication.SetAuthCookie(userId, false); 

 

  • 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.

This way you will end up with a clean, easy-to-test security implementation that satisfies the preconditions of your solution - and nothing else. As a bonus you have avoided dependencies between your security code and ASP.NET - this can make testing easier and makes your security code reusable with other types of GUI.

Tags: ,

.NET development

Comments

2/3/2009 9:07:28 PM #

Claus Konrad

Clever construction, Peter!
Good work.

Claus Konrad Denmark

5/20/2009 1:52:50 PM #

Jan Ehlers

Nice example. However there is a small error in your principal. The field someCustomProperty should have been someCustomProperty1.

Jan Ehlers Denmark

1/17/2011 8:49:46 PM #

bkreeger

Is 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?

bkreeger United States

3/22/2011 3:34:27 PM #

stefan

What is MyBusinessLayerSecurityClass???

I try to implement this Principal in my asp.net MVC 2 application

stefan Switzerland

3/22/2011 4:17:42 PM #

Peter Ravnholt

@bkreeger
Using 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.

Peter Ravnholt Denmark

3/23/2011 7:50:08 PM #

Vinicius Gama

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.

thx

Vinicius Gama Brazil

3/23/2011 8:27:06 PM #

Vinicius Gama

okay, now i understand. The Application_AuthenticateRequest executes more than once.

Vinicius Gama Brazil

8/31/2011 9:01:51 PM #

David

Hello Peter,

Thanks 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.

David Colombia

1/26/2012 12:38:44 PM #

Markus

hi,
shouldn't the custom properties better be placed into a custom IIdentity class?

Markus Germany

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.6.1.0

About the author

Peter Ravnholt

My Delicious bookmarks

Email

Peter Ravnholt is working as a .NET software architect and lead developer.

Widget Twitter not found.

Root element is missing.X

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

 


© Copyright 2009, Peter Ravnholt


Creative Commons License

Peter's .NET Ramblings
is licensed under a Creative Commons Navngivelse 3.0 Unported License.
Permissions beyond the scope of this license may be available at the contact page.