AllowAnonymous not working with Custom AuthorizationAttribute
asp.net MvcAuthenticationAttributesasp.net Web-Apiasp.net Mvc Problem Overview
This has had me stumped for a while. None of the commonly encountered similar situations seem to apply here apparently. I've probably missed something obvious but I can't see it.
In my Mvc Web Application I use the Authorize and AllowAnonymous attributes in such a way that you have to explicitly open up an action as publicly available rather than lock down the secure areas of the site. I much prefer that approach. I cannot get the same behaviour in my WebAPI however.
I have written a custom Authorization Attribute that inherits from System.Web.Http.AuthorizeAttribute with the following:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class MyAuthorizationAttribute : System.Web.Http.AuthorizeAttribute
I have this registered as a filter:
public static void RegisterHttpFilters(HttpFilterCollection filters)
{
filters.Add(new MyAuthorizationAttribute());
}
This all works as expected, actions are no longer available without credentials. The problem is that now the following method will not allow the AllowAnonymous attribute to do it's thing:
[System.Web.Http.AllowAnonymous]
public class HomeController : ApiController
{
[GET("/"), System.Web.Http.HttpGet]
public Link[] Index()
{
return new Link[]
{
new SelfLink(Request.RequestUri.AbsoluteUri, "api-root"),
new Link(LinkRelConstants.AuthorizationEndpoint, "OAuth/Authorize/", "authenticate"),
new Link(LinkRelConstants.AuthorizationTokenEndpoint , "OAuth/Tokens/", "auth-token-endpoint")
};
}
}
The most common scenario seems to be getting the two Authorize / AllowAnonymous attributes mixed up. System.Web.Mvc is for web apps and System.Web.Http is for WebAPI (as I understand it anyway).
Both of the Attributes I'm using are from the same namespace - System.Web.Http. I assumed that this would just inherit the base functionality and allow me to inject the code I need in the OnAuthotize method.
According to the documentation the AllowAnonymous attribute works inside the OnAuthorize method which I call immediately:
public override void OnAuthorization(HttpActionContext actionContext)
{
base.OnAuthorization(actionContext);
Any thought's would be really appreciated.
Has anyone encountered this problem before and found the root cause?
asp.net Mvc Solutions
Solution 1 - asp.net Mvc
In the AuthorizeAttribute there is the following code:
private static bool SkipAuthorization(HttpActionContext actionContext)
{
Contract.Assert(actionContext != null);
return actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any()
|| actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();
}
Include this method in your AuthorizeAttribute class then add the following to the top of your OnAuthorization method to skip authorization if any AllowAnonymous attributes are found:
if (SkipAuthorization(actionContext)) return;
Solution 2 - asp.net Mvc
ASP.NET MVC 4:
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
or
private static bool SkipAuthorization(AuthorizationContext filterContext)
{
Contract.Assert(filterContext != null);
return filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Any()
|| filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Any();
}
Solution 3 - asp.net Mvc
In my case, none of the above solutions worked.
I am using .Net Core 3.1 with a custom IAuthorizationFilter
and I had to do the following:
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any()) return;
Solution 4 - asp.net Mvc
Using MVC 5
Steps to overcome this issue:-
-
Update your Anonymous attribute of WebAPI project and make it like
[System.Web.Mvc.AllowAnonymous]
-
Now go to your custom attribute class and write the code
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext filterContext) { if (filterContext == null) { throw new UnauthorizedAccessException("Access Token Required"); } base.OnAuthorization(filterContext); if (filterContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any()) { return; } if (filterContext.Request.Headers.Authorization != null) { var response = PTPRestClient.GetRequest(filterContext.Request.Headers.Authorization.ToString(), "api/validate/validate-request"); if (!response.IsSuccessStatusCode) { throw new UnauthorizedAccessException(); } } else { throw new UnauthorizedAccessException("Access Token Required"); } }
Solution 5 - asp.net Mvc
Here is a solution for ASP.NET Core 2+ and ASP.NET Core 3+. Add it into IAsyncAuthorizationFilter implementation:
private static bool HasAllowAnonymous(AuthorizationFilterContext context)
{
var filters = context.Filters;
return filters.OfType<IAllowAnonymousFilter>().Any();
}
And check like this:
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if(HasAllowAnonymous(context))
return;
}
Solution 6 - asp.net Mvc
Using C#6.0 Create a static class that extends the ActionExecutingContext.
public static class AuthorizationContextExtensions {
public static bool SkipAuthorization(this ActionExecutingContext filterContext) {
Contract.Assert(filterContext != null);
return filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Any()|| filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Any();
}
}
Now your override filterContext will be able to call the extension method, just make sure they are in the same namespace, or include the proper using statement.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeCustomAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (filterContext.SkipAuthorization()) return;// CALL EXTENSION METHOD
/*NOW DO YOUR LOGIC FOR NON ANON ACCESS*/
}
}
Solution 7 - asp.net Mvc
I must be using a different version of the .net framework or web api but hopefully this helps someone:
bool skipAuthorization = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any() || actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();
if (skipAuthorization)
{
return;
}
Solution 8 - asp.net Mvc
public class MyAuthorizationAuthorize : AuthorizeAttribute, IAuthorizationFilter
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
if (skipAuthorization) return;
}
else filterContext.Result = new HttpUnauthorizedResult();
}
}