HttpClient
Authentication Headers
Single Instance
.NET Core
C# Programming

HttpClient single instance with different authentication headers

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Reusing HttpClient is a good practice, but reusing authentication headers globally is usually not. The right model is to keep the HttpClient instance long-lived while attaching authentication to each individual request. That gives you connection reuse without leaking one caller’s credentials into another request.

Keep the Client, Vary the Request

HttpClient manages connection pools underneath, so creating one client per request can waste sockets and degrade performance. That is why modern .NET guidance favors long-lived clients or IHttpClientFactory.

The mistake is setting DefaultRequestHeaders.Authorization differently for concurrent requests on the same shared client. Default headers are shared mutable state. If you change them per request, one request can affect another.

Set Authentication on HttpRequestMessage

The safe pattern is to build a request object and put the auth header on that request.

csharp
1using System;
2using System.Net.Http;
3using System.Net.Http.Headers;
4using System.Threading.Tasks;
5
6class Program
7{
8    private static readonly HttpClient Client = new HttpClient();
9
10    static async Task Main()
11    {
12        using var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data");
13        request.Headers.Authorization =
14            new AuthenticationHeaderValue("Bearer", "token-123");
15
16        using HttpResponseMessage response = await Client.SendAsync(request);
17        Console.WriteLine(response.StatusCode);
18    }
19}

This keeps the HttpClient reusable while the credentials stay scoped to a single request.

Support Multiple Authentication Schemes

You can send Basic auth to one endpoint and Bearer auth to another using the same client instance.

csharp
1using System;
2using System.Net.Http;
3using System.Net.Http.Headers;
4using System.Text;
5using System.Threading.Tasks;
6
7class Program
8{
9    private static readonly HttpClient Client = new HttpClient();
10
11    static async Task Main()
12    {
13        string basicToken = Convert.ToBase64String(
14            Encoding.UTF8.GetBytes("user:password")
15        );
16
17        using var basicRequest = new HttpRequestMessage(HttpMethod.Get, "https://example.com/basic");
18        basicRequest.Headers.Authorization =
19            new AuthenticationHeaderValue("Basic", basicToken);
20
21        using var bearerRequest = new HttpRequestMessage(HttpMethod.Get, "https://example.com/bearer");
22        bearerRequest.Headers.Authorization =
23            new AuthenticationHeaderValue("Bearer", "token-abc");
24
25        await Client.SendAsync(basicRequest);
26        await Client.SendAsync(bearerRequest);
27    }
28}

The client stays shared. The credentials change per request.

When DefaultRequestHeaders Are Appropriate

Default headers are useful only when the same header truly applies to every request the client sends.

For example, a client that always calls one service with one API key can be configured like this:

csharp
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "shared-key");

That is fine if the key is fixed for the life of that client. It is not fine when different users or destinations require different credentials.

Prefer IHttpClientFactory in ASP.NET Core

In server applications, IHttpClientFactory is often the cleanest way to manage shared clients. It gives you pooled handlers and lets you create named or typed clients for different remote services.

Example registration:

csharp
1builder.Services.AddHttpClient("inventory", client =>
2{
3    client.BaseAddress = new Uri("https://inventory.example.com/");
4});

Then you still attach user-specific or request-specific auth on the outgoing HttpRequestMessage.

csharp
1public class InventoryGateway
2{
3    private readonly IHttpClientFactory _factory;
4
5    public InventoryGateway(IHttpClientFactory factory)
6    {
7        _factory = factory;
8    }
9
10    public async Task<HttpResponseMessage> GetAsync(string token)
11    {
12        var client = _factory.CreateClient("inventory");
13        using var request = new HttpRequestMessage(HttpMethod.Get, "items");
14        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
15        return await client.SendAsync(request);
16    }
17}

Separate Clients When the Configuration Is Truly Different

If two backends require different base addresses, proxies, certificates, or retry policies, separate clients can be cleaner than one universal client. The key rule is not "one HttpClient for the entire process no matter what." The real rule is "do not create throwaway clients per request when a shared client or client factory fits better."

Common Pitfalls

The biggest mistake is mutating DefaultRequestHeaders.Authorization on a shared client before every call. In concurrent code, that can send the wrong credentials. Another mistake is creating a brand-new HttpClient per request just to avoid shared headers, which trades correctness for connection inefficiency. Developers also forget that HttpRequestMessage is the natural place for per-request auth. In ASP.NET Core, skipping IHttpClientFactory can make lifetime and configuration management harder than it needs to be.

Summary

  • Reuse HttpClient, but do not treat authentication headers as global state.
  • Put auth headers on each HttpRequestMessage when credentials vary by request.
  • Use DefaultRequestHeaders only for headers that truly apply to every request.
  • 'IHttpClientFactory is a strong default in ASP.NET Core applications.'
  • Separate clients are fine when remote services need different base configuration.
  • Avoid both per-request client creation and per-request mutation of shared default auth headers.

Course illustration
Course illustration

All Rights Reserved.