Search code examples
authenticationasp.net-corejwtidentityserver4asp.net-core-webapi

Why does my authentication middleware pass a request to the authorization filter when no token is supplied?


I have a .NET Core 1.1 Web API which uses JWT authentication middleware and a custom authorization filter. They are defined like this:

services.AddSingleton<IAuthorizationRequirement, MercuryEndpointAuthorizationHandler>();
services.AddSingleton<IEndpointAuthorizeDictionary, EndpointRights>();
services.AddSingleton<IAuthorizationHandler, MercuryEndpointAuthorizationHandler>();

services.AddAuthorization(options =>
{
    options.AddPolicy("AuthorizeMercuryEndpoint", policy =>
    {
        policy.AddRequirements(services.BuildServiceProvider().GetService<IAuthorizationRequirement>());
    });
});

and

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    TokenValidationParameters = tokenValidationParameters,
    Events = new JwtBearerEvents()
    {
        OnMessageReceived = async (context) =>
        {
            Debug.WriteLine("====>  JWT Message received");
        },
        OnTokenValidated = async (context) =>
        {
            Debug.WriteLine("====>  JWT token validated");
            context.HttpContext.Items["JwtTokenIsValid"] = true;
        },
        OnAuthenticationFailed = async (context) =>
        {
            Debug.WriteLine("====>  JWT token failed auth");
            context.HttpContext.Items["JwtTokenIsValid"] = false;
            if ((AuthenticationType.IdServer & authTypes) != 0)
                context.SkipToNextMiddleware();
        }
    }
});

When I call an endpoint protected with [Authorize("AuthorizeMercuryEndpoint")] and a valid JWT token, the call succeeds as expected, and I see the following debug written by the process:

====>  JWT Message received
====>  JWT token validated
====>  Request authorized (when the auth filter succeeds)

However, if I do not pass a token, the JWT middleware appears to pass the request straight to the authorization filter without attempting authentication - neither OnTokenValidated nor OnAuthenticationFailed are called, and when authorization fails (because there is no authenticated identity) the pipeline is terminated.

This is a problem because I want to pass the request to a second authentication middleware if the JWT token validation fails the initial authentication.

Does anyone know what the recommended approach for this is?


Solution

  • The JWT middleware does apply the validation part only if it finds a token in the HTTP request, as you can see here.

    The Authorize attribute now contains an ActiveAuthenticationSchemes property which defines which authentication schemes will be executed to try to authenticate the user. The advantage of that is you can now remove the AutomaticAuthenticate in the JWT middleware options, and it will be executed lazily when needed - like, if the user hits an action that doesn't require authentication, it will not execute.

    One thing you might not like, though, is that MVC won't stop after an authentication scheme was successful, as you can see here. If that's a trade-off you're willing to take, I think it's a good way to go.