C# / .NETDevOpsMisc
C# / .NET
Authentication in Http Client Factory
Alexandru Puiu
Alexandru Puiu
December 21, 2022
1 min

Named Http Clients created by an Http Client Factory is an efficient way to manage calls to 3rd party services such Google’s Captcha and other APIs your app as a unit is a client of.

  • This post isn’t meant for services used on behalf of users of the application. There are special considerations to be made in such cases, which I’ll cover in a future post.

Injecting authentication in the Http Client Factory

First, let’s take a look at how we might handle Google Captcha

We register a new Http Client named captcha, setting a timeout of 5 seconds, and a retry policy of 5 times with a 300ms backoff period, and recording metrics with prometheus, served by our /metrics endpoint

services.AddHttpClient("captcha", config =>
{
    config.BaseAddress = new Uri("https://www.google.com/");
    config.Timeout = new TimeSpan(0, 0, 5);
})
    .ConfigurePrimaryHttpMessageHandler(() => { return new SocketsHttpHandler { UseCookies = false }; })
    .AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromMilliseconds(300 * retryAttempt)))
    .UseHttpClientMetrics();

Now that we have it registered, when we need to use it, we just inject a IHttpClientFactory and get the client from a pool

using (var client = _clientFactory.CreateClient("captcha"))
{
    var response = await client.PostAsync("recaptcha/api/siteverify", new FormUrlEncodedContent(new Dictionary<string, string?> { { "secret", _captchaSettings.Value.Secret }, { "response", model.Token }, { "remoteip", Request.HttpContext.Connection.RemoteIpAddress?.ToString() } }), ct);
    response.EnsureSuccessStatusCode();

    var captchaResponse = await response.Content.ReadFromJsonAsync<CaptchaResponse>(cancellationToken: ct);
    if (!captchaResponse!.Success)
    {
        ModelState.AddModelError(nameof(model.Token), "Invalid captcha!");
        _logger.LogWarning("Invalid captcha sending message");

        return ValidationProblem(ModelState);
    }

    ...
}

Now let’s consider a case where we need to retrieve an access token that’s valid for an hour, and we want to reuse it, and have it automatically added to any request made to that service. In this case let’s use an identity provider, in an app that would make a lot calls to such a service to manage users, reset passwords, etc.

We inject a MemoryCache instance to store our token for slightly less than its expiration time, to give us room for our calls to complete before expiration, in this case a wide margin of 2 minutes is used. We’ll also inject our IOptions of Autehntication Settings, and we’ll get our token using OAuth 2.0 / client_credentials, with all the things we added previously.

services.AddHttpClient("idP", (sp, httpClient) =>
{
    var cache = sp.GetRequiredService<IMemoryCache>();
    var authenticationSettings = sp.GetRequiredService<IOptions<AuthenticationSettings>>();
    httpClient.BaseAddress = new authenticationSettings.Value.IdPBaseUrl;

    var token = cache.Get<string>("IdPToken");
    if (token == null)
    {
        var tokenResponse = httpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
        {
            Address = authenticationSettings.Value.Authority + "/connect/token",

            ClientId = authenticationSettings.Value.IdPClientId,
            ClientSecret = authenticationSettings.Value.IdPClientSecret,
            Scope = authenticationSettings.Value.Scope
        }).ConfigureAwait(false).GetAwaiter().GetResult();

        token = tokenResponse.AccessToken;
        cache.Set("IdPToken", token, new TimeSpan(0, 0, tokenResponse.ExpiresIn - 180));
    }
                
    httpClient.SetBearerToken(token);
})
    .ConfigurePrimaryHttpMessageHandler(() => { return new SocketsHttpHandler { UseCookies = false }; })
    .AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(20, retryAttempt => TimeSpan.FromMilliseconds(300 * retryAttempt)))
    .UseHttpClientMetrics();

Now, anywhere we need to make calls to our Identity Provider, we can just create an HttpClient from the IHttpClientFactory and our authentication is handled for us, efficently.

using (var httpClient = _clientFactory.CreateClient("idP"))
{
    ...
}

Tags

utilssecurity
Alexandru Puiu

Alexandru Puiu

Engineer / Security Architect

Systems Engineering advocate, Software Engineer, Security Architect / Researcher, SQL/NoSQL DBA, and Certified Scrum Master with a passion for Distributed Systems, AI and IoT..

Expertise

.NET
RavenDB
Kubernetes

Social Media

githubtwitterwebsite

Related Posts

RavenDB Integration Testing
Using RavenDB in Integration Testing
December 24, 2022
2 min

Subscribe To My Newsletter

I'll only send worthwhile content I think you'll want, less than once a month, and promise to never spam or sell your information!
© 2022, All Rights Reserved.

Quick Links

Get In TouchAbout Me

Social Media