XN16
XN16

Reputation: 5879

How to add JWT Bearer Token to ALL requests to an API

I'm in the process of trying to put together a small project which uses Asp.Net Core Identity, Identity Server 4 and a Web API project.

I've got my MVC project authenticating correctly with IdS4 from which I get a JWT which I can then add to the header of a request to my Web API project, this all works as expected.

The issue I have is how I'm actually adding the token to the HttpClient, basically I'm setting it up for every request which is obviously wrong otherwise I'd have seen other examples online, but I haven't been able to determine a good way to refactor this. I've read many articles and I have found very little information about this part of the flow, so I'm guessing it could be so simple that it's never detailed in guides, but I still don't know!

Here is an example MVC action that calls my API:

[HttpGet]
[Authorize]
public async Task<IActionResult> GetFromApi()
{
    var client = await GetHttpClient();
    string testUri = "https://localhost:44308/api/TestItems";
    var response = await client.GetAsync(testUri, HttpCompletionOption.ResponseHeadersRead);
    var data = await response.Content.ReadAsStringAsync();

    GetFromApiViewModel vm = new GetFromApiViewModel()
    {
        Output = data
    };
    return View(vm);
}

And here is the GetHttpClient() method which I call (currently residing in the same controller):

private async Task<HttpClient> GetHttpClient()
{
    var client = new HttpClient();
    var expat = HttpContext.GetTokenAsync("expires_at").Result;
    var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
    if ((dataExp - DateTime.Now).TotalMinutes < 10)
    {
         //SNIP GETTING A NEW TOKEN IF ITS ABOUT TO EXPIRE
    }

    var accessToken = await HttpContext.GetTokenAsync("access_token");
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

    return client;
}

My StartUp classes are pretty standard from what I gather, but if they could be useful, then I'll add them in.

Upvotes: 4

Views: 11087

Answers (2)

Michael Shterenberg
Michael Shterenberg

Reputation: 1016

You can use HttpRequestMessage

// Create this instance once on stratup 
// (preferably you want to keep an instance per base url to avoid waiting for socket fin)
HttpClient client = new HttpClient();

Then create an instance of HttpRequestMessage:

HttpRequestMessage request = new HttpRequestMessage(
  HttpMethod.Get, 
  "https://localhost:44308/api/TestItems");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "ey..");
await client.SendAsync(request);

Upvotes: 0

Stephen Cleary
Stephen Cleary

Reputation: 456717

I've read many articles and I have found very little information about this part of the flow, so I'm guessing it could be so simple that it's never detailed in guides, but I still don't know!

The problem is that the docs are really spread all over, so it's hard to get a big picture of all the best practices. I'm planning a blog series on "Modern HTTP API Clients" that will collect all these best practices.

First, I recommend you use HttpClientFactory rather than new-ing up an HttpClient.

Next, adding an authorization header is IMO best done by hooking into the HttpClient's pipeline of message handlers. A basic bearer-token authentication helper could look like this:

public sealed class BackendApiAuthenticationHttpClientHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _accessor;

    public BackendApiAuthenticationHttpClientHandler(IHttpContextAccessor accessor)
    {
        _accessor = accessor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var expat = await _accessor.HttpContext.GetTokenAsync("expires_at");
        var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
        if ((dataExp - DateTime.Now).TotalMinutes < 10)
        {
             //SNIP GETTING A NEW TOKEN IF ITS ABOUT TO EXPIRE
        }

        var token = await _accessor.HttpContext.GetTokenAsync("access_token");

        // Use the token to make the call.
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        return await base.SendAsync(request, cancellationToken);
    }
}

This can be hooked up via DI:

services.AddTransient<BackendApiAuthenticationHttpClientHandler>();
services.AddHttpClient<MyController>()
    .ConfigureHttpClient((provider, c) => c.BaseAddress = new Uri("https://localhost:44308/api"))
    .AddHttpMessageHandler<BackendApiAuthenticationHttpClientHandler>();

Then you can inject an HttpClient into your MyController, and it will magically use the auth tokens:

// _client is an HttpClient, initialized in the constructor
string testUri = "TestItems";
var response = await _client.GetAsync(testUri, HttpCompletionOption.ResponseHeadersRead);
var data = await response.Content.ReadAsStringAsync();

GetFromApiViewModel vm = new GetFromApiViewModel()
{
    Output = data
};
return View(vm);

This pattern seems complex at first, but it separates the "how do I call this API" logic from "what is this action doing" logic. And it's easier to extend with retries / circuit breakers / etc, via Polly.

Upvotes: 12

Related Questions