TheMethod
TheMethod

Reputation: 3001

JwtBearerHandler with authorization header for discovery endpoint

I'm using ASP.NET Core 2.1 with the JwtBearer scheme for authentication, like so:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(o =>
        {
            // my options here
        });
}

Pretty standard, however the hitch I've run into is my token authorities discovery endpoint requires authorization to access. What I need to do is inject a basic authentication header into the request the JWT middleware does to my authorities endpoint. I'm looking at the configuration options and I'm not seeing anything that allows me to do that. I'm also looking through the source code to see if I can see where the http request is being made but it's pretty obfuscated through abstraction and extension methods and I haven't found it yet.

Would anyone be able to tell me if it is possible to set this somewhere or will I have to create my own authorization handler and tie things together manually? Any advice is appreciated.

Upvotes: 1

Views: 6385

Answers (1)

poke
poke

Reputation: 388103

By default, the JwtBearer authentication scheme will use a ConfigurationManager for the configuration that’s exposed at .well-known/openid-configuration. If you do not configure an instance explicitly by setting the JwtBearerOptions.ConfigurationManager property, then it will automatically create one after your configuration:

var httpClient = new HttpClient(options.BackchannelHttpHandler ?? new HttpClientHandler());
httpClient.Timeout = options.BackchannelTimeout;
httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB

options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, new OpenIdConnectConfigurationRetriever(),
    new HttpDocumentRetriever(httpClient) { RequireHttps = options.RequireHttpsMetadata });

You could explicitly set the configuration manager before, so that this code doesn’t run. That way, you can provide your own implementation or a HttpClient that authorizes its requests properly.

If you look at the code, there is also a BackchannelHttpHandler. This is the HttpClientHandler that is being used for backchannel communications. Backchannel requests are all those that communicate with the authority; so not only the configuration endpoint but also things like the userinfo endpoint for OAuth. Since JWT does not have any backchannel communication, you do not need to worry about it in this case, but since you can also configure that for e.g. OAuth, it’s important to keep it in mind.

So using that BackchannelHttpHandler, you can for example specify credentials:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(o =>
    {
        options.BackchannelHttpHandler = new HttpClientHandler()
        {
            UseDefaultCredentials = true,
        };

        // …
    });

If you need other credentials, for example using an Authorization HTTP header, then you can also provide your own DelegatingHandler that adds the HTTP header before delegating the request to the normal HttpClientHandler. Something like this:

public class AuthorizingHandler : DelegatingHandler
{
    private readonly string _headerValue;
    public AuthorizingHandler(HttpMessageHandler inner, string headerValue)
        : base(inner)
    {
        _headerValue = headerValue;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Add("Authorization", _headerValue);
        return base.SendAsync(request, cancellationToken);
    }
}
var innerHandler = new HttpClientHandler();
var headerValue = "Bearer foobar";
options.BackchannelHttpHandler = new AuthorizingHandler(innerHandler, headerValue);

Another alternative would be to just provide the configuration directly using JwtBearerOptions.Configuration. But of course that requires you to sync the settings with the authority in other ways, so it likely isn’t a good option.

Upvotes: 9

Related Questions