0

I am trying to implement a very basic login scheme for my MVC3 site. If I understand correctly, instead of adding the [Authorize] markup to each of my controller classes, I should be able to simply implement a global setting. To accomplish this, I have added the following into global.asax:

protected void Application_Start()
{
    RegisterGlobalFilters(GlobalFilters.Filters);
}

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new AuthorizeAttribute());  

}

and in my webconfig, I added:

<authentication mode="Forms">
   <forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>

The result is that the resulting page is totally blank. Looking at the url, it seems that mvc is redirecting to my login route as expected except the page empty. If I comment out the code in global.asax and just place the [Authorize] markup directly in each contoller, it works as expected.

As a workaround, I have implemented what I have read the MVC2 best practice to be, which was to create a BaseController:Controller class, add the [Authorize] markup to it, and then change the inherentences of all of my controllers to inheret from BaseController instead of Controller.

That seems to work well enough for now.

But why isn't the global.asax implementation working?

NewJoizey
  • 73
  • 11

1 Answers1

3

Let's see what's happening here:

  1. You are navigating to /
  2. Your global authorize attribute kicks in and since the user is not authenticated he is redirected to ~/Account/LogOn (as instructed in your web.config file) for authentication
  3. Your global authorize attribute kicks in and since the user is not authenticated he is redirected to ~/Account/LogOn (as instructed in your web.config file) for authentication
  4. Same as 3.
  5. Same as 4.
  6. ...

I think you get the point. The LogOn action should be excluded from authentication otherwise the user can never get a chance to login to your web site.

Since you have applied the Authorize attribute globally this cannot be done. One possible way is to write a custom AuthorizeAttribute that will be applied globally and which will exclude this action from authentication.

So you could write a marker attribute:

public class AllowAnonymousAttribute : Attribute
{
}

and a global custom authorize attribute:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var exclude = ((AllowAnonymousAttribute[])filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), false)).Any();
        if (!exclude)
        {
            base.OnAuthorization(filterContext);
        }
    }
}

that will be registered:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new MyAuthorizeAttribute());  
}

Now all that's left for you is to decorate the controller actions that you want to be excluded from authentication with our marker attribute:

public class AccountController : Controller
{
    [AllowAnonymous]
    public ActionResult LogOn()
    {
        return View();
    }

    [AllowAnonymous]
    [HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        ...
    }
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Thanks Darin, that's certainly laid out very clearly and helps a lot. Much appreciated. – NewJoizey Jul 18 '12 at 15:32
  • @NewJoizey, did you manage to get it working? Do you still have further questions on this subject? – Darin Dimitrov Jul 18 '12 at 16:41
  • Darin, Thanks for following up. I mentioned in the original post that I did get this working using the MVC2 workaround which was good enough for the time being. I couldn't understand the "why" behind the observed behavior, but you explained this under "Let's see what is happening here". Now that I understand it, I will probably leave it to a future refactor to implement. – NewJoizey Jul 31 '12 at 20:13