I have implemented a JWT-based authentication by inheriting DelegatingHandler
and adding the class as configuration.MessageHandlers.Add(new MyDelegatingHandler())
.
When implementing DelegatingHandler
, I override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
. The logic is simple there - I retrieve a token from Authorization
header, check it's validity. If it is valid - I set Thread.CurrentPrincipal
and HttpContext.Current.User
, otherwise I return new HttpResponseMessage(HttpStatusCode.Unauthorized)
Basically it looks like this (very simplified):
public class TokenValidationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = GetTokenFromAuthorizeHeader(request);
if (TokenIsValid(token)) {
var principal = CreatePrincipal(token);
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
return base.SendAsync(request, cancellationToken);
} else {
// TODO: fix
return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
}
}
Now this method is called even on WebApi method that has [AllowAnonymous]
attribute with it. This is good because I want to set principal even if the method allows anonymous. But this logic fails if the supplied token in Authorization
header is invalid.
User sends a request to resource with [AllowAnonymous]
and with an invalid token in Authorization
header, which should come through because the resourse allows anonymous, but my code checks authorization, find out that the token is invalid and sends HttpStatusCode.Unauthorized
.
The fix would be to check where // TODO: fix
is whether the resource the user is accessing allows anonymous or not, and only send HttpStatusCode.Unauthorized
if it is not, but I can't figure out how to properly do this.
How can I do this?
One of the new features in ASP.NET MVC 4 is the AllowAnonymous Attribute that helps you secure an entire ASP.NET MVC 4 Website or Controller while providing a convenient means of allowing anonymous users access to certain controller actions, like the login and register Actions.
[AllowAnonymous] bypasses all authorization statements. If you combine [AllowAnonymous] and any [Authorize] attribute, the [Authorize] attributes are ignored. For example if you apply [AllowAnonymous] at the controller level, any [Authorize] attributes on the same controller (or on any action within it) are ignored.
There are four ways to authenticate when calling a web API: API key authentication. Basic authentication. OAuth 2.0 Client Credentials Grant.
Use Authentication Filters, which Web API 2 introduced. They perform just authentication, but say nothing about if user has access to a resource or not. Exactly what you need.
public class JwtAuthenticationFilter : IAuthenticationFilter
{
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var token = GetTokenFromAuthorizeHeader(context.Request);
if (TokenIsValid(token)) {
var principal = CreatePrincipal(token);
// Use context.Principal instead of Thread.CurrentPrincipal
// and HttpContext.Current.User whenever.
context.Principal = principal;
}
return Task.CompletedTask;
}
// TODO: Implement remaining IAuthencitaionFilter members.
}
Apply this JwtAuthenticationFilter
globally to perform authentication for all requests:
// httpConfig is an instance of HttpConfiguration
httpConfig.Filters.Add(new JwtAuthenticationFilter());
In this way user is authenticated if token is OK. But still all users have access to your API - even the ones with invalid token. Let's move further and protect it.
AuthorizeAttribute is what you need to restrict access to Web API from unauthenticated users. You can apply it globally by using the same approach as above:
httpConfig.Filters.Add(new AuthorizeAttribute());
The one who doesn't have valid token won't pass. Good. The last step is to allow access to some particular resources for users with invalid tokens.
From now on AllowAnonymousAttribute
should work. Basically AuthorizeAttribute
just checks if resource is marked by [AllowAnonymous]
and skips authorization in this case.
Generally Web API is protected from unauthenticated users, but some resources can disable authorization by applying [AllowAnonymous]
. JwtAuthenticationFilter
we implemented above works in any case, so users with valid token will be always authenticated - even if a resource allows anonymous access.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With