2

I'm using IdentityModel.AspNetCore Package to access a protected API using client credential flow.
In my startup, I have the following configuration

services.AddAccessTokenManagement(options =>
{
    options.Client.Clients.Add("oauth", new ClientCredentialsTokenRequest
    {
        Address = Configuration.GetValue<string>("Endpoint"),
        ClientId = Configuration.GetValue<string>("ClientId"),
        ClientSecret = Configuration.GetValue<string>("ClientSecret"),
        Scope = Configuration.GetValue<string>("Scope")
    });
});
services.AddClientAccessTokenClient("client", configureClient: client =>
{
    client.BaseAddress = new Uri(Configuration.GetValue<string>("ApiBaseUrl"));
});

In my service I'm getting a client instance using IHttpClientFactory

var client = clientFactory.CreateClient("client");

This code is working fine, and I can access the API.
My question is, when I expand the client instance I get from clientFactory, the Authorization header in there is null.
So I'm very confused how this is working.
I expected that it'll have Authorization header value set with the bearer token details.
So how is this working? How is the bearer token set by IdentityModel?
(API is correctly authorizing it seems as if I change the client secret it'll give a 401)

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
Nilmi Nawalage
  • 191
  • 3
  • 18

1 Answers1

1

.NET allows you to attach DelegatingHandlers to an HttpClient to intercept and modify the requests & responses. After you send a request, it goes through a stack of handlers before actually being sent through the network.

public class MessageHandler1 : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Process request");
        // Call the inner handler.
        var response = await base.SendAsync(request, cancellationToken);
        Debug.WriteLine("Process response");
        return response;
    }
}

IdentityModel library works the same way. It intercepts the request, and adds the Authorization handler before sending it. Then it checks the response for HTTP 401 errors, and refreshes the token and repeats the request.

Here's how it works (source):

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    await SetTokenAsync(request, forceRenewal: false, cancellationToken);
    var response = await base.SendAsync(request, cancellationToken);

    // retry if 401
    if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
    {
        response.Dispose();

        await SetTokenAsync(request, forceRenewal: true, cancellationToken);
        return await base.SendAsync(request, cancellationToken);
    }

    return response;
}

As for the actual Authorization header that seems to be missing, you can find it inside HttpResponseMessage.RequestMessage property.

From the docs (emphasis mine):

This property is set to the request message which led to this response message. In the case of a request sent using HttpClient, this property will point to the actual request message leading to the final response. Note that this may not be the same message the user provided when sending the request. This is typically the case if the request needs to be resent due to redirects or authentication. This property can be used to determine what URL actually created the response (useful in case of redirects)


Further references:

abdusco
  • 9,700
  • 2
  • 27
  • 44