in·dom·i·ta·ble
adj.   Incapable of being overcome, subdued, or vanquished; unconquerable.

6th
FEB

Forms Authentication in Asp.Net MVC

Posted by indomitablehef | Filed under Asp.Net MVC, TDD

This is part 1 of 4
Forms Authentication in Asp.Net MVC

I’ve been working on a testable solution for my Asp.Net MVC project. I have a solution that I think works, but I’d love to hear comments/criticisms if you’ve got any.

First, set up forms authentication in web.config:

    <authentication mode="Forms">
      <forms loginUrl="Membership/Login" defaultUrl="/" />
    </authentication>

Now you’ll need an interface for IAuthenticationProvider

1
2
3
4
5
 public interface IAuthenticationProvider
    {
        bool IsAuthenticated(ControllerContext context);
        void RedirectToLogin(ControllerContext context);
    }

And an implementation, FormsAuthenticationProvider

1
2
3
4
5
6
7
8
9
10
11
12
public class FormsAuthenticationProvider: IAuthenticationProvider
    {
        public virtual bool IsAuthenticated(ControllerContext context)
        {
            return context.HttpContext.User.Identity.IsAuthenticated;
        }
 
        public virtual void RedirectToLogin(ControllerContext context)
        {
            context.HttpContext.Response.Redirect(FormsAuthentication.LoginUrl + string.Format("?ReturnUrl={0}", context.HttpContext.Request.Url.AbsolutePath), true);
        }
    }

I’m doing authentication on Controllers using an ActionFilterAttribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
     public class RequiresAuthenticationAttribute : ActionFilterAttribute
    {
        private IAuthenticationProvider authenticationProvider;
 
        public RequiresAuthenticationAttribute(IAuthenticationProvider provider) {
            authenticationProvider = provider;    
        }
 
        public RequiresAuthenticationAttribute() : this(ServiceLocator.Current.GetInstance<IAuthenticationProvider>()) {}
 
 
 
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (!authenticationProvider.IsAuthenticated(filterContext)) {
                authenticationProvider.RedirectToLogin(filterContext);
            }
        }
    }

A couple of things to notice about this:

I have a test-specific constructor, that allows me to pass in the IAuthentication provider. I’m using Castle Windsor as an IoC container, and the LocatorProvider for ServiceLocator. In my parameter-less constructor, I simply get the configured IAuthenticationProvider from the IoC container.

To use my RequiresAuthentication attribute, I just decorate the controller with it, like this

1
2
3
4
5
6
7
8
    [HandleError]
    [RequiresAuthentication]
    public class HomeController : Controller
    {
        public ActionResult Index() {
            return View();
        }
    }

Now for my tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[TestFixture]
    public class RequiresAuthenticationAttributeTests
    {
        [Test]
        public void CanRedirectToLoginWhenNotAuthenticated() {
 
            //arrange
            var mockedProvider = MockRepository.GenerateMock<IAuthenticationProvider>();
            mockedProvider.Expect(x => x.IsAuthenticated(null)).IgnoreArguments().Return(false);
            mockedProvider.Expect(x => x.RedirectToLogin(null)).IgnoreArguments();
            //act 
            var attribute = new RequiresAuthenticationAttribute(mockedProvider);
            attribute.OnActionExecuting(null);
            //assert
            mockedProvider.VerifyAllExpectations();
        }
 
        [Test]
        public void CanPassThroughWhenAuthenticated()
        {
            //arrange
            var mockedProvider = MockRepository.GenerateMock<IAuthenticationProvider>();
            mockedProvider.Expect(x => x.IsAuthenticated(null)).IgnoreArguments().Return(true);
            //act 
            var attribute = new RequiresAuthenticationAttribute(mockedProvider);
            attribute.OnActionExecuting(null);
            //assert
            mockedProvider.AssertWasNotCalled(x => x.RedirectToLogin(null));
            mockedProvider.VerifyAllExpectations();
        }
 
    }

Using Rhino Mocks, I just mock the IAuthenticationProvider and pass it in to the constructor of my RequiresAuthenticationAttribute.

The code inside the FormsAuthenticationProvider relies on httpContext, which is damn near impossible to mock…I tried. So with this solution, I’ve at least isolated that code so that I can test everything else. I would LOVE to hear your thoughts on this…be brutal.

Update: The authentication section of Web.config indicates that the loginurl is Membership/Login….that means you’ll need a MembershipController with a Login action to display the login view, like this

1
2
3
4
5
6
7
public class MembershipController: Controller
    {
        public ActionResult Login()
        {
            return View("Login");
        }
    }

Update: continue on to Part II, where we dig into the MembershipController

Reader's Comments

  1. Praveen Angyan |

    Finally! Someone who has written a comprehensive guide to authentication and authorization in ASP.NET MVC, including Membership providers.

    One small question. ASP.NET MVC Version 1.0 ships with an [Authorize] attribute already provided. Shouldn’t you be using that? Just curious.

  2. indomitablehef |

    Maybe so. I tend to separate the concept of “Authorize” from “Authenticate”. In the examples here, I’m only dealing with Authentication, but I think the [Authorize] attribute would handle that as well.

    In the app I was working on when I wrote these posts, I ended up with a BaseController that had the [Authenticate] attribute and a PublicController base controller that did not. In my AdminController, I had an [AuthorizeFor] attribute. In the OnActionExecuting method for that attribute, I called into an AuthorizationService, to check for the role specified in the attribute, e.g., [AuthorizeFor(Role=”Administrator”)]

    I did some looking around, and found this post on how to override the Asp.Net MVC AuthorizeAtrribute. In hindsight, I would have used that attribute instead of my AuthorizeFor attribute.

  3. indomitablehef |

    “this post” == http://urlzen.com/bwm

Leave a Reply