I'm working on a web service that has APIs which all require authentication. Authentication is performed with a global message handler configured in the WebApiConfig
like so:
config.MessageHandlers.Add(new AuthMessageHandler());
Now we would like to expose a new API which does not perform authentication. My initial approach was to configure a per-route handler for this new API, but from what I have read in various places (such as the Microsoft docs about HTTP message handlers) the global message handlers are called before the HttpRoutingDispatcher
, so requests will still pass through the global handlers before hitting any per-route handlers. I've come up with a few possible solutions:
Configure per-route handlers for all routes. We have very many APIs, but because of the way that the code is written I think this can be as little as a one-line addition and deletion. I don't like it though, because we have to be a lot more careful about adding new routes in the future to ensure they are properly authenticated. Plus, with the sheer number of APIs we support I don't feel confident that I can necessarily cover all of them. There's a reason we do it globally.
Modify the handler to have some sort of whitelist for routes that bypass authentication. I would like to avoid this because it complicates the handler code. Plus it's in a completely different common codebase and I don't really want to have to go digging around in there...
As you can probably tell, I'd like to avoid both of these solutions if possible. Is there a way to exclude certain routes from the global message handlers? Maybe that would defeat the purpose of having a global handler in the first place, but it would be nice if this were somehow possible.
As suggested in comment on the original question, the solution was to use the AllowAnonymousAttribute
on the controller which had the anonymous APIs. This alone didn't solve the issue, as we do not use the AuthorizeAttribute
for which the AllowAnonymousAttribute
was built, but our handler could use this check to determine whether to authorize the request or not:
private bool AllowAnonymousAccess(HttpRequestMessage request)
{
IHttpControllerSelector selector = GlobalConfiguration.Configuration.Services.GetHttpControllerSelector();
HttpControllerDescriptor descriptor = selector.SelectController(request);
Type controllerType = descriptor.ControllerType;
return controllerType.GetCustomAttribute<AllowAnonymousAttribute>(true) != null;
}
Note that this only works at the controller level. I was unable to find a quick solution for determining which controller method would be called and whether that method has the attribute, as this is apparently not quite so easy to do at this point in the request chain.