Search code examples
c#asp.net-coredotnet-httpclienthttpclientfactory

How to add a cookie to a request using HttpClient from IHttpClientFactory


Background
I'm working on an ASP.NET Core Web API where we within our API call a 3rd party API. This 3rd party API requires every request to contain a cookie with an access token. Our API gets this token from a claim (from the ClaimsPrincipal of the user associated with the request).

Details
This answer shows how to set a cookie on a request, but the example requires that one manually constructs the HttpClient (to be able to inject a HttpClientHandler with a CookieContainer). And since it is not desirable to do manual instantiation of the HttpClient, I would rather get the HttpClient injected through DI, which this example illustrates (usin IHttpClientFactory).

In my service, I have access to a an injected HttpClient instance (_httpClient), and the most simple function looks like this:

public async Task<string> GetStatusAsync(ClaimsPrincipal user)
{
    // TODO: Add cookie with access token (from user's claims) before making request
    return await _httpClient.GetStringAsync(Endpoints.Status);
}

This GitHub issue asks about how to include authorization cookie when using IHttpClientFactory, and the answer says to use the header propagation middleware. And now to my issue:

From what I can see, you set up the header propagation middleware (with all headers and cookies and whatnot) during service configuration at application startup. In our API however, I do not have the value for the authentication cookie before actually making the request to the 3rd party API.

Question
How can I add a cookie to the request on a HttpClient instance that is injected to my service using IHttpClientFactory, right before making the actual request?


Solution

  • The solution was indeed to use header propagation. I just had to get all the pieces together correctly.

    Header propagation is configured inside ConfigureServices (in Startup.cs). And since I want to use data from the current user's claims, the trick is, when adding the cookie header, to use on of the overloads that takes in a function that gives you access to the current context:

    services.AddHeaderPropagation(options =>
    {
        options.Headers.Add("Cookie", context =>
        {
            var accessToken = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "access-token")?.Value;
            return accessToken != null ? new StringValues($"token={accessToken}") : new StringValues();
        });
    });
    

    In addition to this, since I'm using a Typed Service, I also had to configure that that specific service should forward the cookie header (at the same place inside ConfigureServices in Startup.cs):

    services.AddHttpClient<IApiService, ApiService>(httpClient =>
    {
        httpClient.BaseAddress = new Uri("https://httpbin.org/");
    }).AddHeaderPropagation(options =>
    {
        options.Headers.Add("Cookie");
    });
    

    And finally, the thing that caused me some trouble for a little while: Since I'm using data from the current users claims, the registration of the header propagation middleware (app.UseHeaderPropagation(); inside Configure in Startup.cs) must happen after adding the authentication middleware (app.UseAuthentication();). If not, the claims haven't been set yet.

    And as a final tip: I head great us in https://httpbin.org/ when working on this. The request inspection endpoints there are really useful to see what data you actually pass along in your request.