29
votes

I have a controller and I would like to require Authorization for all actions by default except a couple. So in the example below all actions should require authentication except the Index. I don't want to decorate every action with the Authorize, I just want to override the default authorization in certain circumstances probably with a custom filter such as NotAuthorize.

[Authorize]
public class HomeController : BaseController
{
    [NotAuthorize]
    public ActionResult Index()
    {
        // This one wont
        return View();
    }

    public ActionResult About()
    {
        // This action will require authorization
        return View();
    }
}
7

7 Answers

41
votes

Ok, this is what I did. If there is a better way let me know.

public class NotAuthorizeAttribute : FilterAttribute
{
    // Does nothing, just used for decoration
}

public class BaseController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Check if this action has NotAuthorizeAttribute
        object[] attributes = filterContext.ActionDescriptor.GetCustomAttributes(true);
        if (attributes.Any(a => a is NotAuthorizeAttribute)) return;

        // Must login
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
    }
}
33
votes

What about [AllowAnonymous] ??

10
votes

MVC4 has a new attribute exactly meant for this [AllowAnonymous] (as pointed out by Enrico)

[AllowAnonymous]
public ActionResult Register()

Read all about it here:

http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx

6
votes

Here's what I would do, similar to Craig's answer with a couple of changes:

1) Create an ordinary attribute deriving from System.Attribute (no need to derive from FilterAttribute since you aren't going to be using anything FilterAttribute provides).

Maybe create a class hierarchy of attributes so you can test based on the hierarchy, e.g.

Attribute
    AuthorizationAttribute
         AuthorizationNotRequiredAttribute
         AuthorizationAdminUserRequiredAttribute
             AuthorizationSuperUserRequiredAttribute

2) In your BaseController override the OnAuthorization method rather than the OnActionExecuting method:

protected override void OnAuthorization(AuthorizationContext filterContext)
{
    var authorizationAttributes = filterContext.ActionDescriptor.GetCustomAttributes(true).OfType<AuthorizationAttribute>();
    bool accountRequired = !authorizationAttributes.Any(aa => aa is AuthorizationNotRequiredAttribute);

I like the approach of being secure by default: even if you forget to put an attribute on the Action it will at least require a user to be logged in.

5
votes

Mark the controller with [Authorize]

[Authorize] public class YourController : ApiController

Mark actions you want public with :

[AllowAnonymous]

1
votes

Little late to the party, but I ended up creating a Controller-level auth attribute and an Action-level auth attribute and just skipping over the Controller auth if the Action had its own Auth attribute. See code here:

https://gist.github.com/948822