The below C# code creates a sub-class of the System.Net.Http.HttpClient that deals with adding the "Authorization" header to subsequent calls, using OAuth client credentials grant.
using System;
using System.Collections.Generic;
using System.Net.Http;
using Newtonsoft.Json.Linq;

namespace SomeProject.WebAPI.Client
{
    internal sealed class SomeWebApiHttpClient : HttpClient
    {
        private static string authToken;
        private static DateTime authTokenTime;

        private SomeWebApiHttpClient(ISomeWebApiClientConfig config)
        {
            this.BaseAddress = new Uri(config.SomeWebApiBaseAddress);
        }
        
        public static async Task<SomeWebApiHttpClient> CreateInstanceAsync(ISomeWebApiClientConfig config)
        {
            var instance = new KineticHttpClient(config);
            instance.DefaultRequestHeaders.Add("Authorization", await GetAuthAsync(config.SomeWebApiTenantId, config.CallingClientId, config.CallingClientKey));

            return instance;
        }

        private static async Task<string> GetAuthAsync(string tenantId, string callingClientId, string callingClientKey)
        {
            if (string.IsNullOrEmpty(authToken) || DateTime.Now.Subtract(authTokenTime).TotalMinutes > 45)
            {
                using (var authClient = new HttpClient())
                {
                    authClient.BaseAddress = new Uri("https://login.microsoftonline.com/");

                    Dictionary<string, string> bodyContent = new Dictionary<string, string>();
                    bodyContent.Add("grant_type", "client_credentials");
                    bodyContent.Add("client_id", callingClientId);
                    bodyContent.Add("client_secret", callingClientKey);
                    bodyContent.Add("resource", "https://somedomain.onmicrosoft.com/APP-URI-ID");

                    var result = await authClient.PostAsync(
                        $"{tenantId}/oauth2/token",
                        new FormUrlEncodedContent(bodyContent)).ConfigureAwait(false);

                    if (result.IsSuccessStatusCode)
                    {
                        dynamic tokenResult = (dynamic)JValue.Parse(await result.Content.ReadAsStringAsync().ConfigureAwait(false));

                        authToken = $"{tokenResult.token_type} {tokenResult.access_token}";
                        authTokenTime = DateTime.Now;
                    }
                    else
                    {
                        Trace.WriteLine($"Failed to login to API - {await result.Content.ReadAsStringAsync().ConfigureAwait(false)}");
                        
                        return string.Empty;
                    }
                }
            }

            return authToken;
        }
    }
}
In the above code you must correctly set the domain and APP-URI-ID for the WebAPI this client is supposed to be using (or pass them in the config if they are variable). The configuration is passed in via an interface so that the calling code can inject whatever implementation they have for storing the configuration. E.g. In the web project composition root, a class that reads from the web.config using ConfigurationManager. The interface is defined as below:
public interface ISomeWebApiClientConfig
    {
        string SomeWebApiBaseAddress { get; }
        string SomeWebApiTenantId { get; }
        string CallingClientId { get; }
        string CallingClientKey { get; }
    }
These settings can be found in the Azure portal within the active directory that defines the client/server applications. Now the rest of the code base can use this client to call the API's endpoints, without worrying about injecting the auth header. Example usage of the HttpClient to call an example API endpoint:
using System.Collections.Generic;
using System.Diagnostics;
using Newtonsoft.Json.Linq;

namespace SomeProject.WebAPI.Client
{
    public class ExampleDataClient
    {
        private readonly ISomeWebApiClientConfig config;

        public ExampleDataClient(ISomeWebApiClientConfig config)
        {
            this.config = config;
        }

        public async Task<IEnumerable<SomeExampleData>> GetSomeExampleData(string someParam)
        {
            using (var client = await SomeWebApiHttpClient.CreateInstanceAsync(this.config).ConfigureAwait(false))
            {
                var result = await client.GetAsync($"api/SomeExampleData").ConfigureAwait(false);

                if (result.IsSuccessStatusCode)
                {
                    var returnValue = JValue.Parse(await result.Content.ReadAsStringAsync().ConfigureAwait(false));

                    if (returnValue.HasValues)
                    {
			List<SomeExampleData> outputList = new List<SomeExampleData>();
                       foreach (var item in returnValue)
                       {
                           outputList.Add(SomeExampleData.FromJObject(item));
                       }
                       
                       return outputList.AsEnumerable();
                    }
                    else
                    {
                        return Task.FromResult<IEnumerable<SomeExampleData>>(null);
                    }
                }
                else
                {
                    Trace.WriteLine($"HTTP error: {result.RequestMessage.RequestUri} ({result.StatusCode}) - {await result.Content.ReadAsStringAsync().ConfigureAwait(false)}");
                    return Task.FromResult<IEnumerable<SomeExampleData>>(null);
                }
            }
        }
    }
}