Identityserver4: WWW-Authenticate header in Unauthorized response

Created on 3 Feb 2018  路  18Comments  路  Source: IdentityServer/IdentityServer4

I'm reproducing the question published on Stackoverflow as it didn't get any answer and because I am wondering if this may be related to the Identity Server 4 implementation.

WWW-Authenticate header in Identity Server 4 Unauthorized response

As I understand the OAuth spec here, when the resource server returns a Unauthorized response it should include the WWW-Authenticate header and that header could include the scope required to access that specific resource.

I'm having the issue that when a client app (using client credentials) does not include the access token in the request, I get the Unauthorized response but the WWW-Authenticate header is missing.

Could it be that I am missing something the Identity Server config in my Web API? Or is it something with Identity Server itself? Any help would be very much appreciated. Thanks.

Here is my Web API setup of Identity Server:

services
    .AddAuthorization(
        (options) =>
        {
            options.AddPolicy(
                Policies.Monitoring, 
                (policy) =>
                {
                    policy.RequireClaim("scope", Policies.Scopes.Monitoring);
                });
            options.AddPolicy(
                Policies.VatNumber,
                (policy) =>
                {
                    policy.RequireClaim("scope", Policies.Scopes.VatNumber);
                    policy.Requirements.Add(new ClientSubscriptionRequirement());
                });
        });
services.AddSingleton<IAuthorizationHandler, ClientSubscriptionAuthorizationHandler>();
services
    .AddAuthentication("Bearer")
    .AddIdentityServerAuthentication(
        (options) =>
        {
            options.Authority = "http://localhost:5000";
            options.RequireHttpsMetadata = false;
            options.ApiName = "datalookup";
        });
bug report

All 18 comments

The JWT handler from Microsoft (which we use internally) has not implemented that specific feature. You can handle the JWT events though and add the header yourself.

Well, I have tried that like this:

services
    .AddAuthentication("Bearer")
    .AddIdentityServerAuthentication(
        (options) =>
        {
            options.Authority = "http://localhost:5000";
            options.RequireHttpsMetadata = false;
            options.ApiName = "datalookup";
            options.JwtBearerEvents.OnChallenge = async (context) =>
            {
                context.Response.StatusCode = 401;
                context.Response.Headers.Append(HeaderNames.WWWAuthenticate, context.Options.Challenge);

                if (!string.IsNullOrEmpty(context.Error))
                {
                    await context.Response.WriteAsync(context.Error);
                }

                (...)

                context.HandleResponse();
            };
        });

But the event handler never gets called and I cannot figure out why.
I suspect that when no token is provided the error is returned before the JWT handler is called.
May be IDS is missing an extensibility point in this particular case, don't you think?

This is really unrelated to IdentityServer - you can try to use the plain JwtBearer handler from Microsoft to see if your scenario is supported (I can't remember). That's what we use under the covers.

I have the same issue.

@hugoqribeiro, the handler never gets called because it is internally instantiated by [IdentityServer4.AccessTokenValidation] (https://github.com/IdentityServer/IdentityServer4.AccessTokenValidation/blob/3fd88d9ae73734504e269b08901779a7bac1484f/src/IdentityServer4.AccessTokenValidation/IdentityServerAuthenticationOptions.cs#L148) module. (Should IdentityServerAuthenticationOptions.JwtBearerEvents property be marked as internal to avoid confusion if not editable by end-user ?)

And since we cannot configure JwtBearerEvents ourselves, we cannot use with the default JwtBearerHandler class implementation if we hope to somehow change its behaviour.
Does this mean we need to override class JwtBearerHandler to do the job we want ? I hardly see that play nice with IS4 ..

I hope my reasoning is somewhat coherent with the situation, the whole thing is hard to grasp ... I just don't know where to go next to have the server to return me this 'WWW-Authenticate' header on error 401 ..

@darkurse that was exactly my point! And that is why I think that IDS4 needs some new extensibility point to override/listen to those events.

I don't agree with this question being closed. It makes sense.

a) We do use the events

https://github.com/IdentityServer/IdentityServer4.AccessTokenValidation/blob/2ea977807e27c22a98325e8b2a9a6fa94b0f6dc3/src/IdentityServer4.AccessTokenValidation/IdentityServerAuthenticationOptions.cs#L148-L159

b) have you tried my suggestion of using the plain JwtBearerHandler to make sure it behaves as expected?

We are happily using the JwtBearerEvents, and returning the WWW Authenticate header on the 'OnAuthenticationFailed' event.

.AddIdentityServerAuthentication(options =>
            {               
                options.Authority = Configuration["Authentication:IdServer:Instance"];
                options.ApiName = Configuration["Authentication:IdServer:ApiResource"];
                options.ApiSecret = Configuration["Authentication:IdServer:ApiSecret"];
                options.RequireHttpsMetadata = true;
                options.SupportedTokens = SupportedTokens.Both;
                options.EnableCaching = true;
                options.JwtBearerEvents = new JwtBearerEvents
                {
                    OnAuthenticationFailed = OnAuthenticationFailed,
                    OnTokenValidated = GetUserInfoIfRequired,
                };
            });


private Task OnAuthenticationFailed(AuthenticationFailedContext context)
        {
            if(context.Exception is SecurityTokenExpiredException expiredException)
            {
                context.Response.Headers.TryAdd(HeaderNames.WWWAuthenticate, 
                    new StringValues(new[] {
                        JwtBearerDefaults.AuthenticationScheme,
                        "error=\"invalid_token\"",
                        "error_description=\"The access token expired\""
                    }));
            }
}

@chemass, I have changed my code to match what you're doing and I added a breakpoint in OnAuthenticationFailed handler. But it never stops there when I fail authentication.

None of the breakpoints below is caught. So there might be smthing else you're doing ??

```C#
services
.AddAuthentication(o =>
{
o.DefaultScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
o.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = _configuration.GetValue("Authority_Endpoint", DEFAULT_DEVELOPMENT_AUTHORITY);
options.RequireHttpsMetadata = _configuration.GetValue("Authority_IsHttps", DEFAULT_IS_HTTPS_AUTHORITY);
options.ApiSecret = configuration.GetValue("Api_Not_That_Secret");
options.ApiName = "api";
options.EnableCaching = true;
options.CacheDuration = TimeSpan.FromMinutes(30);
options.SupportedTokens = SupportedTokens.Both;

    options.JwtBearerEvents = new JwtBearerEvents
    {
        OnChallenge = context =>
        {
            return Task.CompletedTask; // <-- breakpoint here
        },

        OnAuthenticationFailed = context =>
        {
            return Task.CompletedTask; // <-- breakpoint there
        },

        OnTokenValidated = context =>
        {
            return Task.CompletedTask; // <-- breakpoint and also there
        }
    };
});

```

@chemass:

I have confirmed that your code works fine when the token expires. The event is raised.

But that was not the scenario. The scenario was when the client does not provide any token at all.
And I have also confirmed that in that scenario the event is not called.

The idea here was to implement an authentication callback so that the client would not need to know in advance the authority to which it needs to request the token. The Azure SDK KeyVault library has exactly that:

KeyVaultCredential.cs (on KeyVault client library)

@leastprivilege:

I have not tried that because honestly I don't know how to do it. I am pretty new with Identity Server. Can you point me to some example of how to do that?

does not provide any token at all

This is simply an anonymous call, and if you require the caller to pass a token you need to issue a 401 response. Why not put a middleware in front of the API to check for this response condition and add the header?

There is also a different scenario where the events are not raised and you cannot do the WWW-Authenticate.

The Web API I'm protecting has 2 scopes: one is called "monitoring" and the other is called "calculation".

Now you have a client that gets a valid token for scope "monitoring" and then tries to access an action that requires "calculation". It gets a 403 Forbidden but the AuthenticationFailed does not get called.

Looking at the IDS log I see:

info: IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler[13]
AuthenticationScheme: Bearer was forbidden.

The problem here, I think, is the same as with the "no token scenario". IdentityServerAuthenticationHandler never even calls the JWT handler.

The problem here, I think, is the same as with the "no token scenario". IdentityServerAuthenticationHandler never even calls the JWT handler.

No, that;'s by design and it's Microsoft's code that's doing that. No token simply means anonymous, and you still need something that requires authorization. So on the way out, agreed, Microsoft's code possibly should be adding www-auth response header, but it's not or at least not doing it the way you want. So forget about their programming model and events (that apparently don't get called) and code it yourself with middleware in front of the API. Check for both 401 and 403 and add the response headers you want.

@brockallen but I'm not convinced by the approach that you're suggesting.

I have finally understood how to use the JwtBearerHandler directly and I have confirmed that it works as supposed.

  • If you don't send any token, you receive a 401 with the WWW-Authenticate header without error description.
  • If you send an expired token, you receive a 401 with the WWW-Authenticate header with the correct error_description.
  • The same if you send a token with an invalid audience.
  • All events (OnChallenge, OnAuthenticationFailed) are called.

Comparing the source code for the JwtBearerHandler and for the IdentityServerAuthenticationHandler I think the problem when you send no token is that the latest returns AuthenticateResult.NoResult() but it doesn't override HandleChallengeAsync to build the header (or at least call the event).

I wasn't able to understand why OnChallenge is not called when you send an invalid token. That beats me.

OK - i verified that and you are right - we don't seem to handle the challenge code path correctly.

I am looking into it - thanks for reporting.

OK - I pushed 2.4.0 of the access token validation handler. This should fix the problem.

Thank you @leastprivilege!

@darkurse did you find out why? I have the same problem now.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings