4

I am using UseJwtBearerAuthentication like this

app.UseJwtBearerAuthentication(options =>
{
   options.Authority = Configuration["Urls:IdentityServer"];
   options.RequireHttpsMetadata = false;

   options.Audience = Configuration["Urls:IdentityServer"] + "/resources";
   options.AutomaticAuthenticate = true;
   options.Events = new JwtBearerEvents
   {
        OnAuthenticationFailed = context =>
        {
          context.HandleResponse();   
          return Task.FromResult(0);
        }
   }; 
});

In the diagnostics window in visual studio I see these 2 exceptions:

System.IdentityModel.Tokens.SecurityTokenExpiredException' in System.IdentityModel.Tokens.dll ("IDX10223: Lifetime validation failed. The token is expired.

and down the line

Exception thrown: 'System.ArgumentNullException' in Microsoft.AspNet.Authentication.dll ("Value cannot be null.")

How would go about returning a HTTP 401 Unauthorized?

Carrie Kendall
  • 11,124
  • 5
  • 61
  • 81
sunil
  • 5,078
  • 6
  • 28
  • 33

1 Answers1

7

It's a known bug. Sadly, the workaround you could use in beta8 no longer works in RC1.

Your only option is to write a middleware catching the exception to prevent the server from returning a 500 response. Of course, it's ugly and will potentially hide important exceptions, but it's the only known workaround that works with RC1.

Here's an example:

app.Use(next => async context =>
{
    try
    {
        await next(context);
    }

    catch
    {
        // If the headers have already been sent, you can't replace the status code.
        // In this case, re-throw the exception to close the connection.
        if (context.Response.HasStarted)
        {
            throw;
        }

        // Rethrow the exception if it was not caused by IdentityModel.
        if (!context.Items.ContainsKey("jwt-workaround"))
        {
            throw;
        }

        context.Response.StatusCode = 401;
    }
});

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    RequireHttpsMetadata = false,

    Audience = "http://localhost:54540/",
    Authority = "http://localhost:54540/",

    Events = new JwtBearerEvents
    {
        OnAuthenticationFailed = context =>
        {
            context.HttpContext.Items["jwt-workaround"] = null;

            return Task.FromResult(0);
        }
    };
});
Community
  • 1
  • 1
Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
  • Does this code return all exceptions as 401s? Sorry, don't quite understand the code. – sunil Apr 14 '16 at 17:45
  • Sure. You could browse the IdentityModel repository and list all the exceptions the JWT security token handler might throw instead of catching all the exceptions. – Kévin Chalet Apr 14 '16 at 17:47
  • 1
    If you just want to catch "expired token" errors, update the catch block to only intercept the SecurityTokenExpiredException exceptions. – Kévin Chalet Apr 14 '16 at 17:49
  • I updated my answer to re-throw exceptions that are not caused by the JWT middleware. – Kévin Chalet Apr 14 '16 at 18:22
  • 1
    Ya catching SecurityTokenExpiredException seems to be the way to go. Also, removing `OnAuthenticationFailed` is probably recommended. – sunil Apr 14 '16 at 18:23
  • Catching `SecurityTokenExpiredException` is fine, but don't forget there are many other exceptions that might be thrown and cause a 500 response (e.g invalid audience, invalid issuer, invalid signature) – Kévin Chalet Apr 14 '16 at 18:25
  • Exactly, so `SecurityTokenExpiredException` will catch exactly that and send a HTTP 401. Everything other exception will just send 500. – sunil Apr 14 '16 at 18:26
  • From what I've learned I believe the exception to catch here would be `SecurityTokenValidationException`. – lc. Apr 30 '16 at 07:09