Custom Authentication with MVC 3.0

Tags: asp.net mvc, security

During a friendly code review discussion a week or so ago I realized I’d forgotten my favorite lore of custom authentication/authorization functionality in lieu of ASP.NET P&M. Though I definitely prefer P&M to rolling my own from scratch to the extent that I’ve gone as far as to use it as a pass-through there are some times when P&M is too much – or too little – so custom schemes must be developed. The thing that surprises me each time I observe a custom scheme is the lack of usage of the IPrincipal/IIdentity interfaces. With MVC 3.0 as my major weapon of choice in web development and my recent adoption of P&M, it became obvious that an opportunity had popped up for mixing my favorite old-school .NET trick into my favorite new-school paradigm.

Here’s the thing. If you don’t use the IIdentity and IPrincipal interfaces when you create a custom authentication/authorization solution you’ve completely missed the boat and I guarantee you you will write 100 times more code, that will be more brittle, than you would have ever written had you just used them in the first place.

image

A quick look into what these two interfaces offer is in order. The graphic above sums it up visually. You’ll see that the IIdentity interface is typically considered a requirement for the IPrincipal to exist; obviously this model fits in rather well with MVC’s favoritism to IoC/DI practices.  The principal wraps around the identity, supplying access to a user’s roles. So long as your classes implement the requirements, they can be bound to the HTTP Context, the current thread, basically attached the process so that all the niceties like use of the [Authorize] attribute and role-based location security via the web.config file are possible without tons of rework.

 

The first class needed in any custom implementation is the identity class. The main purpose of this implementation is to represent the user’s name, really, as well as how the user was authenticated and if they are authenticated. Should the IsAuthenticated property be set to false at run-time, code later on assumes the user is an anonymous user.

image

Next will be the CustomPrincipal class. Since this class implements IPrincipal, it can be used to bind itself directly to a thread (or an HTTP Context). Since it can be used in that manner, all the functionality and support offered via web.config-based authorization, use of the [Authorization] attribute, all of that – it will be maintained and you won’t have to write it (or support it, or debug it, and so on). Note how the constructor’s argument is highlighted to illustrate the connection between the two classes.

image

To wrap these classes up via a native event handler or inherited method we’ll create a BaseController. It could – and probably should – be done via an injected service, obviously, but I’m honoring the KISS principle [you say principle, I say principal] for the purposes of this walk-thru. The base controller is shown below and its main purpose in existing – to authorize the active user account prior to allowing execution of the controller action.

image

Caveat: This next part isn’t a recommendation, it’s only used for the purposes of keeping this explanation simple. Forgive me.

Now that we’ve satisfied the authorization part we’ve got to authenticate the user and store their login information somewhere for the life of their… yeah, you guess it, for the life of their session. We’re going to use Session here, okay, but just for the demo. It isn’t a recommendation. Please turn off the tape recorder.

The SimpleSessionPersister class below persists the user’s name into a session variable [ducks under flying tomato]. The login controller action below the utility class just validates that the user provided something for their username and password and, if so, the user’s username is persisted to session for use – by the BaseController, specifically – later during the HTTP request.

image

You can relax now, that part’s over.

Now that the user has been authenticated we can assume that each controller we create (provided it inherit from our custom base) will authorize each individual request per the best-practice role-base security means. Take the following controller action below, which requires the user be authenticated via the [Authorize] attribute and the web.config, also below, which indicates the login page URL as our Login controller action.

image

Should a user try to hit the non-anonymous page they’d be redirected to the login page (see the URL highlighted below). 

image

To review the process we went through to accomplish our very own custom-made end-to-end security paradigm that still allows usage of the .NET built-in role-based security resources, here’s what we had to do:

  • Implement the IIdentity interface in a custom class
  • Inherit from the IPrincipal interface in a custom class
  • Create a way to authenticate the user
  • Create a way to persist the authentication information [a.k.a. username in this example]
  • Override the OnAuthorization method in a base controller and then use that base controller in future controllers

Happy Coding! If you’d like to take a peek at the code for this demo and debug it for better observation as to how it all works together you can download the code from my DropBox account.

23 Comments

  • Calvin said

    Thank you so much, for this..., I don't know why Microsoft don't include this as an example as it is what 90% of people
    will want to do. If they do not want to use the built in ASP.NET DB users/roles/membership authentication.

    Could you now extend this... please, I'm new to .net and mvc, I get the basics but struggle with writing or knowing what i need to override in order to get the result i am looking for.

    could you change this to use cookie authentication... or explain to me what they are doing to achieve this... i know what a cookie is... and know what it is used for... basically it would mean when i close the browser window the cookie will not apply to the new window. i.e i would not still be logged in... anyways that another issue...

    i get that this is saving the username for the remained of the session. but how is the member of and distinct user handled.. e.g.

    I have not tested this as my logic says it wont work. there is something missing:
    ROLE
    [Authorize(Roles = "Administrator")]
    ROLES
    [Authorize(Roles = "Administrator, SuperAdmin")]
    USERS
    [Authorize(Users= "Jon, Phil, Scott, Brad")]
    COMBINE
    [Authorize(Roles = "Administrator", Users= "Jon, Phil, Scott, Brad")]

  • Sven Hoek said

    "We’re going to use Session here, okay, but just for the demo. It isn’t a recommendation. Please turn off the tape recorder." So what is the suggested method for retaining the authentication information for the user?

  • Jan said

    great post! I have the same question as Sven above. Do you have any examples when using cookie?

  • Luis said

    great post! you saved my life, but if sessions isnt a recommendation, what is the suggested method? , and where did you learn all this? I would like to know where I can learn all of that deeply

  • arash karami said

    Hi, thank you for great post for authentication, i develop sample based on your code for MVC3 and EF 4.1 please read this in codeplex: http://codefirstmvc3.codeplex.com/
    Thanks

  • bradygaster said

    That's awesome, Arash! I'm planning on writing an update to this post soon that omits the session usage (this seemed to irritate many...). Good stuff on your code first demo.

  • xuwi said

    Hello! I want to know how to extend the IIdentity members... I want to say, if I add a member called "Surname", I want to access it on the same way as i access to @User.Identity.Name in razor view or User.Identity.Name in a controller class. (@User.Identity.Surname and User.Identity.Surname)

    I tried to add it as a public member, but it is not vissible. There is any way to make it?

    Thank you.

  • rasi said

    thanks for nice article,

    i run your sample it work perfectly !
    but i see its not tightly integrated with IPrincipal maintained by ASP.NET,

    i try to get the user (HttpContext.Current.User) from global.asax in method :

    [CODE]
    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    { System.Security.Principal.IPrincipal user = HttpContext.Current.User;
    }

    protected void Application_BeginRequest()
    { System.Security.Principal.IPrincipal user = HttpContext.Current.User;
    }
    [/CODE]

    both of those method return null from HttpContext.Current.User


    do you know how to automatically set the HttpContext.Current.User ?

    thankyou
    rasi

  • Dave said

    The ASP.NET Membership Provider is trash and always has been trash. Why the hell would you ever say not to role your own. Every time I've been on that were GOOD development teams wouldn't even consider using that piece of trash. We rolled our own and it wasn't rocket science. Yes a bit of work but well worth it in the end.

  • Fontify said

    This is great. I'm new to the MVC 3.0 authentication scheme, looking for info, and stumbled onto this. Still need to read up more on the principal stuff, but this seems to be just what the doctor ordered. Thanks for sharing !

  • Luke Baughan ( @bUKaneer ) said

    This is a genius post that really clarified how to implement this particular piece of functionality.

    Like many I heeded the warning regarding SimpleSessionPersister and implemented Authentication Cookies instead using this post on stack overflow http://stackoverflow.com/questions/4237394/asp-net-mvc-3-using-authentication combined with the IPrincipal and IIdentity implementation from here.

    All round great job! One small note - if you implement the Stack Overflow answer they say to put the authenticate request code in a method called
    PostAuthenticationRequest() {} in Global.asax - this is slightly incorrect and the method should be:

    protected void Application_AuthenticateRequest(Object sender, EventArgs e)
    { }

  • Luke Baughan ( @bUKaneer ) said

    @rasi you can set the Context.Current user in the Global.asax file in the Application_AuthenticateRequest method (the code below is using Cookie based Auth but you can rip that out, use the SimpleSessionPersister to get the Principal then populate the context:

    protected void Application_AuthenticateRequest(Object sender, EventArgs e)
    {
    HttpCookie formsCookie = Request.Cookies[FormsAuthentication.FormsCookieName];

    if (formsCookie != null)
    {
    FormsAuthenticationTicket auth = FormsAuthentication.Decrypt(formsCookie.Value);

    Guid name = new Guid(auth.Name);

    var principal = new Example.Models.Security.CustomPrincipal(new Example.Models.Security.CustomIdentity(auth.Name));

    Context.User = Thread.CurrentPrincipal = principal;
    }
    }

  • Eugene said

    Any advise on how to use roles i.e. [Authorize(Roles="admin")]? Can the above code just be adapted or is there more to it as in writing custom role providers etc...? I set a breakpoint on IsInRole, but it seems it never gets called.

  • Anand singh said

    Very nice article.
    thank you,thank you,thank you,thank you,thank you,thank you,thank you,thank you so much.

  • kiran shrestha said

    ya,it's a grt article.what should i do now to end session and clear all cache history of broswer after log out is done.do post about logout process and no cache history option too.

  • Jlsfernandez said

    Tx for this explanation,

    I would like to ask you for some advice based in your knowledge and interest.
    I'm playing with signalR and I would like to know how do you implement this security with signalR.
    I mean a Iprincipal cookie generated from mvc4 and regenerated from cookie every round page how did you reuse in a signalR hub
    Tx in advance

Add a Comment

Windows Azure Training

Looking for information on Windows Azure? I work with a great team of guys who put together the Windows Azure Training Kit. If you're doing anything with Windows Azure and you have questions, chances are pretty good you'll find what you're after here. 

My SignalR Samples

After having given a few SignalR presentations I decided to start compiling all the code in a centralized repository. Check out my SignalR Samples repository on GitHub.com

Channel 9

One of the coolest parts of my job is being able to host a Channel 9 TV show. Web Camps TV is the show I co-host with Cory Fowler. Check out our episodes on Channel 9, where we talk to Microsoft community members and engineers.