Posts Tagged ‘authentication’

MVC Authentication and Errors

// January 4th, 2008 // 21 Comments » // MVC

NOTE:
This article was written for the December CTP release of the MVC framework. Unfortunately, it does not entirely apply to the Preview 2 release or subsequent releases.

I love working with the recent CTP release of the ASP.Net MVC framework, but it is definitely an early release and is lacking many of the developer friendly features that we have grown to rely upon in WebForms. One such feature is WebForm’s easy to understand authentication model.

In the WebForms world URLs can be referenced in a web.config file and then have authentication rules applied to them. A rule that says that you must be logged in to view a secure page may look something like:

1 <location path=loginRequired.aspx>

2 <system.web>

3 <authorization>

4 <deny users=? />

5 </authorization>

6 </system.web>

7 </location>

A rule that says only users in the Administrators group may view it might look like this:

1 <location path=adminRequired.aspx>

2 <system.web>

3 <authorization>

4 <allow roles=Administrators />

5 <deny users=* />

6 </authorization>

7 </system.web>

8 </location>

It didn’t take long for me to run into the lack of any central authentication scheme in the new MVC framework. I searched around and found some older information from prior to the CTP release posted by Fredrik Normén that seemed to address my issues, but unfortunately one of the features his solution requires did not make its way into the CTP release: attribute based exception handling.

Looking through the code samples on the page you see how he uses the .Net frameworks built in PrincipalPermission attribute (from System.Security.Permissions) to classify an action as demanding the user be in a specific role. If the user is not in that role the .Net framework will throw a SecurityException. What good does that do? Well take a look at line 3 in the below code:

1 [ControllerAction]

2 [PrincipalPermission(SecurityAction.Demand, Role="Admin"]

3 [ExceptionHandler("Error", typeof(SecurityException))]

4 public void Edit(int? id)

5 {

6

7 }

The ExceptionHandler attribute appears to take two values:

  1. The view to render in the event of an error.
  2. The type of error to match against.

So based upon this code the PrincipalPermission will interrogate the user’s roles when the action is requested and if the user is not in the “Admin” role it will throw a SecurityException. At that point the ExceptionHandler will wake up and say “hey I can handle that” and render the view named “Error”. Neat huh? Too bad ExceptionHandler doesn’t exist…

Personally I liked most of the concepts that were introduced in Frederik’s post, so I went ahead and began to implement the ExceptionHandler attribute. Along the way I realized that what was really needed was a way to apply filters to a controller. I’ve seen Ivan Carrero’s controller filter implementation, but I wanted filters that hooked straight into the MVC Controller’s three major lifecycle events: OnPreAction, OnPostAction, and OnError. By doing so I felt I would minimize the difference between code in a filter and code in a controller. Thus was born the FilterController.

Filter Controller

The FilterController is an abstract class deriving from the System.Web.Mvc.Controller. It’s primary purpose is to interrogate itself via reflection when it is created and to then load any attributes that implement the IControllerFilter interface:

1 /// <summary>

2 /// Descendent of the MVC Controller class that adds capability of processing filters specified by attributes.

3 /// </summary>

4 public abstract class FilterController : Controller

5 {

6

7 /// <summary>

8 /// Default constructor.

9 /// </summary>

10 public FilterController()

11 {

12 Filters = GetFilterAttributes();

13 foreach( IControllerFilter filter in Filters )

14 {

15 filter.Initialize(this);

16 }

17 }

The filters are then called for each of the three integration events: OnPreAction, OnPostAction, and OnError. Here is what the OnError event does:

1 /// <summary>

2 /// Passes control of the OnError event on to all filters that want to handle it.

3 /// </summary>

4 /// <param name=”actionName”>The name of the action being requested when the exception was thrown.</param>

5 /// <param name=”methodInfo”>Reflection object representing the action being requested when the exception was thrown.</param>

6 /// <param name=”exception”>The exception thrown while the action was being requested.</param>

7 /// <returns>A boolean denoting the successful handling of the event.</returns>

8 protected override bool OnError( string actionName, MethodInfo methodInfo, Exception exception )

9 {

10 bool handled = false;

11 bool allTrue = true;

12 foreach( IControllerFilter filter in Filters )

13 {

14 if( filter.HandleOnError )

15 {

16 handled = true;

17 if( !filter.OnError( actionName, methodInfo, exception ) )

18 allTrue = false;

19 }

20 }

21 if( !handled )

22 throw exception;

23 return allTrue;

24 }

The OnPreAction and OnPostAction events look almost exactly like the OnError event.

To fulfill my initial goal of obtaining functionality similar to that described in Frederik’s post, I have created two filters:

  • SecurityFilter
  • ErrorHandlerFilter

Security Filter

While the PrincipalPermission attribute used in Frederik’s post handles many security scenarios well, it wasn’t as flexible or keyboard friendly as I would prefer. I created the SecurityFilter and an arrangement of sub-filters to create what I think is an easier solution.

To use the security filter in your controller you must first inherit from FilterController and apply the [SecurityFilter] attribute.

1 using System;

2 using System.Web.Mvc;

3 using SquaredRoot.Mvc;

4

5 namespace Example

6 {

7

8 [SecurityFilter]

9 public class MyController : FilterController

10 {

11

12 }

This alone does nothing, but you are now able to add one or more of the SecurityFilter’s sub-filters to this controller or it’s actions. The sub-filters I have created are:

  • RequireLogin
    Validates that the user is logged in.
  • RequireAnonymous
    Validates that the user is NOT logged in.
  • RequireRole
    Validates that the user is in the specified role.
  • RequireAnyRole
    Validates that the user is in at least one of the specified roles
  • RequireEachRole
    Validates that the user is in every one of the specified roles.

Let’s imagine a controller for a simple bulletin board system. In order to post to this forum you must be logged in, if you want to delete a post you must be in either the “Administrators” role or the “Moderators” role, and if you want to undelete a post you must be in the “Administrators” group. That controller would look something like:

1 [SecurityFilter]

2 [RequireLogin]

3 public class ForumController : FilterController

4 {

5

6 [ControllerAction]

7 public void Post( string message ){ … }

8

9 [ControllerAction,RequireAnyRole( "Administrators", "Moderators" )]

10 public void Delete( int id ){ … }

11

12 [ControllerAction,RequireRole( "Administrators" )]

13 public void Undelete( int id ){ … }

14

15 }

By applying the [RequireLogin] attribute to the class you have applied that filter to all of the actions as well, which means you must be logged in to call the Post method. The other two methods use the appropriate version of the role requirement filters to achieve their goal.

What happens if the filter validations fail? In the case of an anonymous user attempting to access a restricted resource an AnonymousAccessException (which derives from SecurityException) is thrown while all other scenarios throw a SecurityException. What you do with those exceptions leads us to…

Error Handler Filter

Using the above ForumController, let’s add the ErrorHandler filter:

1 [SecurityFilter]

2 [ErrorHandlerFilter]

3 [RequireLogin]

4 public class ForumController : FilterController

5 {

6

7 }

Like with the last filter, this filter by itself does nothing but allow us to use the ErrorHandler sub-filter. Let’s go ahead and add two sub-filters: one to handle security exceptions and one to handle all other exceptions.

In the event of a security exception we’ll render the “AccessDenied” view while all other exceptions will render the “SystemError” view:

1 [SecurityFilter]

2 [ErrorHandlerFilter(ErrorHandlerMode.Render)]

3 [RequireLogin]

4 [ErrorHandler( 1, "AccessDenied", typeof(SecurityException) )]

5 [ErrorHandler( 2, "SystemError", typeof(Exception) )]

6 public class ForumController : FilterController

7 {

8

9 }

First notice that we are now providing the ErrorHandlerFilter attribute with an option that says ErrorHandlerMode.Render. This is because in the event of an error we want the controller to render the view with the name passed in. Later on we’ll look at the other mode: ErrorHandlerMode.Redirect.

Next notice that we are providing three values to each of the two ErrorHandler sub-filters:

  1. The order in which the sub-filter should be processed. This is important because the order the attribute is returned by reflection is unknown.
  2. The name of the view to render. Just like calling RenderView() from an action, this view name must be accessible to the controller (either in the controller’s view directory or in the Shared directory).
  3. The type of the exception to match against.

Keep in mind that these sub-filters could be applied at either the class level or the method level and that method-level sub-filters are processed before class-level sub-filters. We’ll stick with class-level throughout this article.

An “Access Denied” page popping up whenever we try to go somewhere we aren’t allowed to without logging in isn’t the best user experience. Let’s improve it by sending anonymous users to the login page instead. This time however, we don’t want just render the login view, we want to actually redirect to the SecurityController’s Login action. While we’re at it, I’ll show you an example of handling multiple exceptions with one sub-filter:

1 [ErrorHandler( 1, "Login,Security",

2 ErrorHandlerMode.Redirect,

3 typeof(AnonymousAccessException) )]

4 [ErrorHandler( 2, "AccessDenied",

5 typeof(SecurityException) )]

6 [ErrorHandler( 3, "BadArgument",

7 typeof(ArgumentException),

8 typeof(ArgumentNullException),

9 typeof(ArgumentOutOfRangeException) )]

10 [ErrorHandler( 4, "SystemError",

11 typeof(Exception) )]

Here in line 1 we specify not the name of the view to render, but the name of the action and controller to redirect to (in the format “action,controller”). The handler knows to process this as a redirect because we’ve changed the mode for this one sub-filter to ErrorHandlerMode.Redirect. Lines 7, 8, & 9 illustrate the capability for one sub-filter to match against many exceptions.

Download The Code

I hope you find these filters useful. If they don’t happen to match your particular problem, then feel free to write your own filters. To do so you only have to implement the IControllerFilter interface. I’ve attempted to make it even easier to do so by providing a base class named ControllerFilter that already implements the interface and has several hooks for you to take advantage of.

You are free to use or modify this code for anything including commercial purposes. The only restriction I ask is that you do not take credit for this work yourself, but I do not require any specific attribution.

I have packaged the code into four different releases:

NOTE: These projects were built on Vista x64. If you are running a 32-bit version of Windows you may initially have trouble building the source versions below. To fix this you’ll need to re-add the System.Web.Extensions reference. See my comment below for more details.

Full Source Release
The entire solution zipped up.
Controller Filters (Full Source).zip

Example Site & Binaries Release
(Recommended) Just the binaries zipped up with an example site.
Controller Filters (Example Site).zip

Filter Source Release
The source code for the FilterController and filters. No example site.
Controller Filters (Filter Source).zip

Filter Binaries Release
The binaries for the FilterController and filters only. No source.
Controller Filters (Filter Binaries).zip

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

ASP.Net MVC Membership Basics

// December 10th, 2007 // 36 Comments » // MVC

NOTE:
This article was written for the December CTP release of the MVC framework. Unfortunately, it does not entirely apply to the Preview 2 release or subsequent releases.

The MVC bits have finally arrived and I’ve spent a while digesting them. I’ve been waiting for the bits to be released to begin working on a side-project, so the first thing I did after downloading them last night was crank it up and start working on a new ASP.NET MVC Web Application project.

Typically, the first thing I do on a new project is set up the authentication/authorization system. For the project I am working on I want to use the ASP.Net Membership system, but most of the Membership controls do not work with the MVC framework because they require postbacks. I spent some time last night building a Security controller and views for Registration and Login that I thought would be worth sharing.

So far I have implemented basic functionality for Register, Login, and Logout. There are three files we will have to create. We will also have to change the routing table. Let’s start with the SecurityController:

    1 public class SecurityController : Controller

    2 {

    3 

    4     [ControllerAction]

    5     public void Login()

    6     {

    7         RenderView("Login");

    8     }

    9 

   10     [ControllerAction]

   11     public void Register()

   12     {

   13         RenderView( "Register" );

   14     }

   15 

   16     [ControllerAction]

   17     public void Logout()

   18     {

   19         FormsAuthentication.SignOut();

   20         Response.Redirect( "/" );

   21     }

   22 

   23     [ControllerAction]

   24     public void Authenticate( string userName, string password, string rememberMe, string returnUrl )

   25     {

   26         // figure out if username and password are correct

   27         if( Membership.ValidateUser( userName, password ) )

   28         {

   29             // everything is good, create an authticket and go

   30             FormsAuthentication.SetAuthCookie( userName, (rememberMe != null) );

   31             Response.Redirect( returnUrl );

   32         }

   33         else

   34         {

   35             // something was wrong, figure out which and pass it into view

   36             if( Membership.GetUser(userName) == null )

   37                 ViewData["ErrorMessage"] = "Incorrect username.";

   38             else

   39                 ViewData["ErrorMessage"] = "Incorrect password.";

   40             RenderView( "Login" );

   41         }

   42     }

   43 

   44     [ControllerAction]

   45     public void CreateUser( string userName, string emailAddress, string password, string returnUrl )

   46     {

   47         try

   48         {

   49             // try to create user and then login that user

   50             if( Membership.CreateUser( userName, password, emailAddress ) == null )

   51                 throw new MembershipCreateUserException( "An unspecified error occurred." );

   52             FormsAuthentication.SetAuthCookie( userName, true );

   53             Response.Redirect( returnUrl );

   54         }

   55         catch( MembershipCreateUserException e )

   56         {

   57             // something went wrong

   58             ViewData["ErrorMessage"] = e.Message;

   59             RenderView("Register");

   60             return;

   61         }

   62     }

   63 

   64 }

 

So we have two basic actions methods that only display views (Register and Login) and three action-only methods that have no views (CreateUser, Authenticate, Logout).

I have chosen to not have a logout page, but to instead redirect to the homepage. Switch out ‘Response.Redirect( "/" );’ for ‘RenderView( "Logout" );’ and create a Logout.aspx view if you would like to display a logout message.

Now let’s look at the Login view:

    1 <asp:Content ID="content" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">

    2 

    3     <h2>Login</h2>

    4 

    5     <% if( ViewData["ErrorMessage"] != null ){ %>

    6     <p><% =ViewData["ErrorMessage"] %></p>

    7     <% } %>

    8 

    9     <% using(Html.Form( "Authenticate", "Security" )){ %>

   10     <fieldset>

   11         <legend>Login</legend>

   12         <div><label for="userName">User Name:</label> <% =Html.TextBox( "userName" ) %></div>

   13         <div><label for="password">Password:</label> <% =Html.Password( "password" ) %></div>

   14         <div><label for="rememberMe">Remember Me:</label>

   15             <input type="checkbox" id="rememberMe" name="rememberMe" checked="checked" value="checked" /></div>

   16         <div><% =Html.SubmitButton() %></div>

   17         <% =Html.Hidden( "returnUrl", "/" ) %>

   18     </fieldset>

   19     <% } %>

   20 

   21 </asp:Content>

 

The toolkit’s Html.Checkbox(…) method annoys me. More on why in another post. For now I’ve instead just written the html out by hand. You’ll note I’ve also linked the label to the checkbox with JavaScript so that clicking the label toggles the checkbox.

Then the Register view:

    1 <asp:Content ID="content" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">

    2 

    3     <h2>Register</h2>

    4 

    5     <% if( ViewData["ErrorMessage"] != null ){ %>

    6     <p><% =ViewData["ErrorMessage"] %></p>

    7     <% } %>

    8 

    9     <% using(Html.Form( "CreateUser", "Security" )){ %>

   10     <fieldset>

   11         <legend>Register</legend>

   12         <div><label for="userName">User Name:</label> <% =Html.TextBox( "userName" ) %></div>

   13         <div><label for="emailAddress">Email Address:</label> <% =Html.TextBox( "emailAddress" ) %></div>

   14         <div><label for="password">Password:</label> <% =Html.Password( "password" ) %></div>

   15         <div><% =Html.SubmitButton() %></div>

   16         <% =Html.Hidden( "returnUrl", "/" ) %>

   17     </fieldset>

   18     <% } %>

   19 

   20 </asp:Content>

 

Another straightforward view. Not much to discuss here.

And finally, let’s add the new routes:

    1 RouteTable.Routes.Add( new Route

    2 {

    3     Url = "Login",

    4     Defaults = new {

    5         controller = "Security",

    6         action = "Login" },

    7     RouteHandler = typeof( MvcRouteHandler )

    8 } );

    9 RouteTable.Routes.Add( new Route

   10 {

   11     Url = "Register",

   12     Defaults = new {

   13         controller = "Security",

   14         action = "Register" },

   15     RouteHandler = typeof( MvcRouteHandler )

   16 } );

 

I personally like login to be http://website/login and register to be http://website/register, so that is how I have configured it. The other three actions (Logout, Authenticate, and CreateUser) I access via the default route (ex: /Security/Logout).

That’s it! You should now have a working registration/login system. I’ll leave making it pretty with CSS as an exercise for the reader.

I have included all of the code samples above in the below ZIP file. Just unzip it and place the controller into the Controllers directory, the views into the Views/Security directory (which you will have to create), and copy the code from Routes.txt to the appropriate area of your Global.asax.

MVCMembership_v1.2.zip (2.03 kb)

UPDATE (Dec 11): Johan and Steve Harman were kind enough to point out that I had foolishly set the "remember me" checkbox’s label’s "for" attribute to point to the password field instead of the checkbox itself. I have fixed the code above and provided a new zip file (1.1) for download. Thanks guys!

Update (Dec 19): oVan pointed out a bug in the routing rules defined in the routes.txt file. I have updated the zip file with the correct code. Thanks oVan!

Update (Jan 3): James Nail asked a very good question via a comment: what do you set for the loginUrl and defaultUrl in your web.config? Well James, here is how I’ve setup my web.config…

Assuming we’ll be using forms authentication and securing all pages except login and the homepage, place the following inside the <system.web>element:

    1 <authentication mode="Forms">

    2   <forms loginUrl="/Login" defaultUrl="/" />

    3 </authentication>

    4 <authorization>

    5   <deny users="?" />

    6 </authorization>

Then, somewhere outside the <system.web> element add:

    1 <location path="Default.aspx">

    2   <system.web>

    3     <authorization>

    4       <allow users="*" />

    5     </authorization>

    6   </system.web>

    7 </location>

    8 <location path="Security">

    9   <system.web>

   10     <authorization>

   11       <allow users="*" />

   12     </authorization>

   13   </system.web>

   14 </location>

   15 <location path="Login">

   16   <system.web>

   17     <authorization>

   18       <allow users="*" />

   19     </authorization>

   20   </system.web>

   21 </location>

Note that this will grant access to all actions within the Security controller. It is also worth pointing out that, dependent on your setup, your CSS and image files may not load unless you also create a location path for their directory and grant all users access like so:

    1 <location path="Content">

    2   <system.web>

    3     <authorization>

    4       <allow users="*" />

    5     </authorization>

    6   </system.web>

    7 </location>

I have not updated the zip file with these web.config settings; let me know if anyone would prefer that I add it. I hope this helps some of you!

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com