2

I have an ASP.NET Core 2.1 Web Application project that uses JWT tokens for authenticating the Web API that's built-in to the project. It works fine when I run it locally on my machine, but when I deploy it to Azure (with identical environment and app-settings) it simply returns empty HTTP 401 responses to requests from my authenticated clients and I need to find out why so I can fix it.

I enabled logging of every detail in ASP.NET Core, however I never received any useful output.

First, I added Serilog.AspNetCore and the Console sink to the project through NuGet, then configured logging at Verbose level in Program.cs:

public class Program
{
    public static void Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Verbose()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Verbose)
            .MinimumLevel.Override("System", LogEventLevel.Verbose)
            .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Verbose)
            .Enrich.FromLogContext()
            .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate)
            .CreateLogger();

        CreateWebHostBuilder( args ).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(String[] args)
    {
        return WebHost.CreateDefaultBuilder( args )
            .ConfigureLogging( (ctx, cfg ) =>
            {
                cfg.ClearProviders();
            } )
            .UseStartup<Startup>()
            .UseSerilog();
    }
}

But when I run my web-application on Azure (with console stdout logging to file) I got this output:

[04:13:10 Verbose] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker
Authorization Filter: Before executing OnAuthorizationAsync on filter 
Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter.

[04:13:10 Verbose] 
IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler
HandleAuthenticateAsync called

[04:13:10 Debug] 
IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler
AuthenticationScheme: Bearer was not authenticated.

[04:13:10 Information] 
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService
Authorization failed.

[04:13:10 Verbose] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker
Authorization Filter: After executing OnAuthorizationAsync on filter 
Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter.

[04:13:10 Information] 
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker
Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.

[04:13:10 Verbose] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker
Before executing action result Microsoft.AspNetCore.Mvc.ChallengeResult.

[04:13:10 Information] Microsoft.AspNetCore.Mvc.ChallengeResult
Executing ChallengeResult with authentication schemes (["Bearer"]).

[04:13:10 Verbose] 
IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler
Forwarding challenge to scheme: BearerIdentityServerAuthenticationJwt

Note how despite verbose logging, the error messages (repeated below) don't give me any explanation:

AuthenticationScheme: Bearer was not authenticated.
Authorization failed.

I dug around the ASP.NET Core Security source-code to see that JwtBearerHandler.HandleAuthenticateAsync doesn't do much logging of its own, but it does call into the not-open-sourced System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler which does normally do a lot of logging, including detailed reasons (e.g. (with IDX10209-type error codes in strings), but I don't know why it isn't outputting anything I can capture.

How do I log messages from JwtSecurityTokenHandler?

Dai
  • 141,631
  • 28
  • 261
  • 374

1 Answers1

2

I found the problem:

  • My HttpClient (that was sending the HTTP Authorization header Bearer token) was unintentionally sending it to a http:// URI that immediately received a 301 redirect to a https:// URI. The redirect was performed by IIS without the ASP.NET Core pipeline getting involved.
  • The HttpClient class does not re-send the Authorization header following a redirect (this is by-design).
    • I never noticed this because my HttpClient's received HttpResponseMessage had a reference to the original request which had the Authorization header, not the post-redirect request that lacked the header. I had to use Fiddler with the HTTPS proxy to see the second request was lacking the Authorization header.
  • When IdentityServerAuthenticationHandler or ASP.NET Core's own JwtBearerHandler receives a request with no Authorization header it does not call into JwtSecurityTokenHandler at all. To see this, open the JwtBearerHandler.cs file in the ASP.NET Core Security Git repo and look at HandleAuthenticateAsync: It has this logic:

    if (string.IsNullOrEmpty(token))
    {
        string authorization = Request.Headers["Authorization"];
    
        // If no authorization header found, nothing to process further
        if (string.IsNullOrEmpty(authorization))
        {
            return AuthenticateResult.NoResult();
        }
    
  • So in my case, it never actually called JwtSecurityTokenHandler at all, hence the lack of output messages about JWT validation.

  • However the output messages I did receive did not help. They're both misleading:

    • "AuthenticationScheme: Bearer was not authenticated." should have been something like "AuthenticationScheme: No Bearer token was present in the request." instead.
    • And "Authorization failed." should have been "Authorization skipped because no token was present in the request."
  • So in the end, the fix was to change the original request URI's scheme from http:// to https://.

Dai
  • 141,631
  • 28
  • 261
  • 374