Using IHttpClientFactory and Duende AccessToken Management¶
This code sample demonstrates how to configure an ASP.NET Core host to use Duende's client credentials token management. It shows how to register a client with the required token endpoint, client ID, and secret, and how to set up an HttpClient that automatically manages access tokens.
For a detailed explanation of the code flow, see the Web server host tutorial.
Steps¶
- Start the API using the WebAPI sample.
- Run the code below with .NET Interactive in VS Code or Jupyter Lab. You have the following options:
- Sample using HttpClient
- Sample using Refit
Sample using HttpClient¶
In [ ]:
Copied!
#!csharp
#load "../HttpLoggerHelper.csx"
#r "nuget: Duende.AccessTokenManagement, 4.0.1"
#r "nuget: Microsoft.Extensions.Hosting, 9.0.8"
#r "nuget: Microsoft.Extensions.Caching.Memory, 9.0.5"
#r "nuget: Fhi.Authentication.Extensions, 3.0.0"
using Microsoft.Extensions.Hosting;
using Duende.AccessTokenManagement;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
using Fhi.Authentication;
using Fhi.Authentication.ClientCredentials;
using Fhi.Authentication.OpenIdConnect;
async Task RunAsync()
{
var builder = Host.CreateDefaultBuilder();
builder.ConfigureServices(services =>
{
/**********************************************
* Configuration options from appsettings file
***********************************************/
var authority = "https://demo.duendesoftware.com";
var clientId = "m2m.jwt";
var privateJwk = """
{
"d":"GmiaucNIzdvsEzGjZjd43SDToy1pz-Ph-shsOUXXh-dsYNGftITGerp8bO1iryXh_zUEo8oDK3r1y4klTonQ6bLsWw4ogjLPmL3yiqsoSjJa1G2Ymh_RY_sFZLLXAcrmpbzdWIAkgkHSZTaliL6g57vA7gxvd8L4s82wgGer_JmURI0ECbaCg98JVS0Srtf9GeTRHoX4foLWKc1Vq6NHthzqRMLZe-aRBNU9IMvXNd7kCcIbHCM3GTD_8cFj135nBPP2HOgC_ZXI1txsEf-djqJj8W5vaM7ViKU28IDv1gZGH3CatoysYx6jv1XJVvb2PH8RbFKbJmeyUm3Wvo-rgQ",
"dp":"YNjVBTCIwZD65WCht5ve06vnBLP_Po1NtL_4lkholmPzJ5jbLYBU8f5foNp8DVJBdFQW7wcLmx85-NC5Pl1ZeyA-Ecbw4fDraa5Z4wUKlF0LT6VV79rfOF19y8kwf6MigyrDqMLcH_CRnRGg5NfDsijlZXffINGuxg6wWzhiqqE",
"dq":"LfMDQbvTFNngkZjKkN2CBh5_MBG6Yrmfy4kWA8IC2HQqID5FtreiY2MTAwoDcoINfh3S5CItpuq94tlB2t-VUv8wunhbngHiB5xUprwGAAnwJ3DL39D2m43i_3YP-UO1TgZQUAOh7Jrd4foatpatTvBtY3F1DrCrUKE5Kkn770M",
"e":"AQAB",
"kid":"ZzAjSnraU3bkWGnnAqLapYGpTyNfLbjbzgAPbbW2GEA",
"kty":"RSA",
"n":"wWwQFtSzeRjjerpEM5Rmqz_DsNaZ9S1Bw6UbZkDLowuuTCjBWUax0vBMMxdy6XjEEK4Oq9lKMvx9JzjmeJf1knoqSNrox3Ka0rnxXpNAz6sATvme8p9mTXyp0cX4lF4U2J54xa2_S9NF5QWvpXvBeC4GAJx7QaSw4zrUkrc6XyaAiFnLhQEwKJCwUw4NOqIuYvYp_IXhw-5Ti_icDlZS-282PcccnBeOcX7vc21pozibIdmZJKqXNsL1Ibx5Nkx1F1jLnekJAmdaACDjYRLL_6n3W4wUp19UvzB1lGtXcJKLLkqB6YDiZNu16OSiSprfmrRXvYmvD8m6Fnl5aetgKw",
"p":"7enorp9Pm9XSHaCvQyENcvdU99WCPbnp8vc0KnY_0g9UdX4ZDH07JwKu6DQEwfmUA1qspC-e_KFWTl3x0-I2eJRnHjLOoLrTjrVSBRhBMGEH5PvtZTTThnIY2LReH-6EhceGvcsJ_MhNDUEZLykiH1OnKhmRuvSdhi8oiETqtPE",
"q":"0CBLGi_kRPLqI8yfVkpBbA9zkCAshgrWWn9hsq6a7Zl2LcLaLBRUxH0q1jWnXgeJh9o5v8sYGXwhbrmuypw7kJ0uA3OgEzSsNvX5Ay3R9sNel-3Mqm8Me5OfWWvmTEBOci8RwHstdR-7b9ZT13jk-dsZI7OlV_uBja1ny9Nz9ts",
"qi":"pG6J4dcUDrDndMxa-ee1yG4KjZqqyCQcmPAfqklI2LmnpRIjcK78scclvpboI3JQyg6RCEKVMwAhVtQM6cBcIO3JrHgqeYDblp5wXHjto70HVW6Z8kBruNx1AH9E8LzNvSRL-JVTFzBkJuNgzKQfD0G77tQRgJ-Ri7qu3_9o1M4"
}
""";
var scope = "api";
/**********************************************
* Add services used by the authentication flow
* - AddInMemoryDiscoveryService: Used to get token endpoint from authority.
* Tokenendpoint can be set manually if authority does not have discovery endpoint
* - ClientCredentialsAssertionService: Used when using private jwk in authentication flow
* - AddDistributedMemoryCache: Provides token caching capabilities
* - AddClientCredentialsTokenManagement: Add services for authentication, handle in memory token store
***********************************************/
services.AddInMemoryDiscoveryService([authority]);
services.AddTransient<IClientAssertionService, ClientCredentialsAssertionService>();
services.AddDistributedMemoryCache();
services.AddClientCredentialsTokenManagement();
/***********************************************************************************************
* Configure TokenClient with ClientCredentials options used by the HttpClient to authenticate
* *********************************************************************************************/
services
.AddOptions<ClientCredentialsClient>("m2mTokenClient")
.Configure<IDiscoveryDocumentStore>((options, discoveryStore) =>
{
var discoveryDocument = discoveryStore.Get(authority);
options.TokenEndpoint = discoveryDocument?.TokenEndpoint is not null ? new Uri(discoveryDocument.TokenEndpoint) : null;
options.ClientId = ClientId.Parse(clientId);
options.Scope = Scope.Parse(scope);
options.Parameters = new ClientCredentialParametersBuilder()
.AddIssuer(discoveryDocument.Issuer)
.AddPrivateJwk(privateJwk)
.Build();
});
/*****************************************************************************
* Register HttpClients for the APIs to be consumed. Connect the HttpClient
* to the TokenClient abowe so that configuration will be used when authenticate
*****************************************************************************/
services.AddClientCredentialsHttpClient("m2mHttpClient", ClientCredentialsClientName.Parse("m2mTokenClient"), client =>
{
client.BaseAddress = new Uri("https://localhost:7150");
});
services.AddTransient<ITestService, TestService>();
});
var host = builder.Build();
using var scope = host.Services.CreateScope();
var testService = scope.ServiceProvider.GetRequiredService<ITestService>();
await testService.Get();
}
interface ITestService
{
public Task Get();
}
// A service that uses the registerd HttpClient with HttpClientFactory to make requests.
class TestService : ITestService
{
private readonly IHttpClientFactory _factory;
public TestService(IHttpClientFactory factory)
{
_factory = factory;
}
public async Task Get()
{
var client = _factory.CreateClient("m2mHttpClient");
var response = await client.GetAsync("api/v1/integration/health-records");
await HttpLogger.LogRequest(response.RequestMessage);
await HttpLogger.LogResponse(response);
}
}
await RunAsync();
#!csharp
#load "../HttpLoggerHelper.csx"
#r "nuget: Duende.AccessTokenManagement, 4.0.1"
#r "nuget: Microsoft.Extensions.Hosting, 9.0.8"
#r "nuget: Microsoft.Extensions.Caching.Memory, 9.0.5"
#r "nuget: Fhi.Authentication.Extensions, 3.0.0"
using Microsoft.Extensions.Hosting;
using Duende.AccessTokenManagement;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
using Fhi.Authentication;
using Fhi.Authentication.ClientCredentials;
using Fhi.Authentication.OpenIdConnect;
async Task RunAsync()
{
var builder = Host.CreateDefaultBuilder();
builder.ConfigureServices(services =>
{
/**********************************************
* Configuration options from appsettings file
***********************************************/
var authority = "https://demo.duendesoftware.com";
var clientId = "m2m.jwt";
var privateJwk = """
{
"d":"GmiaucNIzdvsEzGjZjd43SDToy1pz-Ph-shsOUXXh-dsYNGftITGerp8bO1iryXh_zUEo8oDK3r1y4klTonQ6bLsWw4ogjLPmL3yiqsoSjJa1G2Ymh_RY_sFZLLXAcrmpbzdWIAkgkHSZTaliL6g57vA7gxvd8L4s82wgGer_JmURI0ECbaCg98JVS0Srtf9GeTRHoX4foLWKc1Vq6NHthzqRMLZe-aRBNU9IMvXNd7kCcIbHCM3GTD_8cFj135nBPP2HOgC_ZXI1txsEf-djqJj8W5vaM7ViKU28IDv1gZGH3CatoysYx6jv1XJVvb2PH8RbFKbJmeyUm3Wvo-rgQ",
"dp":"YNjVBTCIwZD65WCht5ve06vnBLP_Po1NtL_4lkholmPzJ5jbLYBU8f5foNp8DVJBdFQW7wcLmx85-NC5Pl1ZeyA-Ecbw4fDraa5Z4wUKlF0LT6VV79rfOF19y8kwf6MigyrDqMLcH_CRnRGg5NfDsijlZXffINGuxg6wWzhiqqE",
"dq":"LfMDQbvTFNngkZjKkN2CBh5_MBG6Yrmfy4kWA8IC2HQqID5FtreiY2MTAwoDcoINfh3S5CItpuq94tlB2t-VUv8wunhbngHiB5xUprwGAAnwJ3DL39D2m43i_3YP-UO1TgZQUAOh7Jrd4foatpatTvBtY3F1DrCrUKE5Kkn770M",
"e":"AQAB",
"kid":"ZzAjSnraU3bkWGnnAqLapYGpTyNfLbjbzgAPbbW2GEA",
"kty":"RSA",
"n":"wWwQFtSzeRjjerpEM5Rmqz_DsNaZ9S1Bw6UbZkDLowuuTCjBWUax0vBMMxdy6XjEEK4Oq9lKMvx9JzjmeJf1knoqSNrox3Ka0rnxXpNAz6sATvme8p9mTXyp0cX4lF4U2J54xa2_S9NF5QWvpXvBeC4GAJx7QaSw4zrUkrc6XyaAiFnLhQEwKJCwUw4NOqIuYvYp_IXhw-5Ti_icDlZS-282PcccnBeOcX7vc21pozibIdmZJKqXNsL1Ibx5Nkx1F1jLnekJAmdaACDjYRLL_6n3W4wUp19UvzB1lGtXcJKLLkqB6YDiZNu16OSiSprfmrRXvYmvD8m6Fnl5aetgKw",
"p":"7enorp9Pm9XSHaCvQyENcvdU99WCPbnp8vc0KnY_0g9UdX4ZDH07JwKu6DQEwfmUA1qspC-e_KFWTl3x0-I2eJRnHjLOoLrTjrVSBRhBMGEH5PvtZTTThnIY2LReH-6EhceGvcsJ_MhNDUEZLykiH1OnKhmRuvSdhi8oiETqtPE",
"q":"0CBLGi_kRPLqI8yfVkpBbA9zkCAshgrWWn9hsq6a7Zl2LcLaLBRUxH0q1jWnXgeJh9o5v8sYGXwhbrmuypw7kJ0uA3OgEzSsNvX5Ay3R9sNel-3Mqm8Me5OfWWvmTEBOci8RwHstdR-7b9ZT13jk-dsZI7OlV_uBja1ny9Nz9ts",
"qi":"pG6J4dcUDrDndMxa-ee1yG4KjZqqyCQcmPAfqklI2LmnpRIjcK78scclvpboI3JQyg6RCEKVMwAhVtQM6cBcIO3JrHgqeYDblp5wXHjto70HVW6Z8kBruNx1AH9E8LzNvSRL-JVTFzBkJuNgzKQfD0G77tQRgJ-Ri7qu3_9o1M4"
}
""";
var scope = "api";
/**********************************************
* Add services used by the authentication flow
* - AddInMemoryDiscoveryService: Used to get token endpoint from authority.
* Tokenendpoint can be set manually if authority does not have discovery endpoint
* - ClientCredentialsAssertionService: Used when using private jwk in authentication flow
* - AddDistributedMemoryCache: Provides token caching capabilities
* - AddClientCredentialsTokenManagement: Add services for authentication, handle in memory token store
***********************************************/
services.AddInMemoryDiscoveryService([authority]);
services.AddTransient();
services.AddDistributedMemoryCache();
services.AddClientCredentialsTokenManagement();
/***********************************************************************************************
* Configure TokenClient with ClientCredentials options used by the HttpClient to authenticate
* *********************************************************************************************/
services
.AddOptions("m2mTokenClient")
.Configure((options, discoveryStore) =>
{
var discoveryDocument = discoveryStore.Get(authority);
options.TokenEndpoint = discoveryDocument?.TokenEndpoint is not null ? new Uri(discoveryDocument.TokenEndpoint) : null;
options.ClientId = ClientId.Parse(clientId);
options.Scope = Scope.Parse(scope);
options.Parameters = new ClientCredentialParametersBuilder()
.AddIssuer(discoveryDocument.Issuer)
.AddPrivateJwk(privateJwk)
.Build();
});
/*****************************************************************************
* Register HttpClients for the APIs to be consumed. Connect the HttpClient
* to the TokenClient abowe so that configuration will be used when authenticate
*****************************************************************************/
services.AddClientCredentialsHttpClient("m2mHttpClient", ClientCredentialsClientName.Parse("m2mTokenClient"), client =>
{
client.BaseAddress = new Uri("https://localhost:7150");
});
services.AddTransient();
});
var host = builder.Build();
using var scope = host.Services.CreateScope();
var testService = scope.ServiceProvider.GetRequiredService();
await testService.Get();
}
interface ITestService
{
public Task Get();
}
// A service that uses the registerd HttpClient with HttpClientFactory to make requests.
class TestService : ITestService
{
private readonly IHttpClientFactory _factory;
public TestService(IHttpClientFactory factory)
{
_factory = factory;
}
public async Task Get()
{
var client = _factory.CreateClient("m2mHttpClient");
var response = await client.GetAsync("api/v1/integration/health-records");
await HttpLogger.LogRequest(response.RequestMessage);
await HttpLogger.LogResponse(response);
}
}
await RunAsync();
Sample using Refit¶
Note! This sample is not running due to a Refit. Refit uses C# source generators to generate the client code at compile-time — and source generators do not run in .NET Interactive (Jupyter)
In [ ]:
Copied!
#!csharp
#r "nuget: Duende.AccessTokenManagement, 4.0.1"
#r "nuget: Microsoft.Extensions.Hosting, 9.0.8"
#r "nuget: Microsoft.Extensions.Http, 9.0.0"
#r "nuget: Microsoft.Extensions.Caching.Memory, 9.0.5"
#r "nuget: Fhi.Authentication.Extensions, 3.0.0"
#r "nuget: Refit, 8.0.0"
#r "nuget: Refit.HttpClientFactory, 8.0.0"
using Microsoft.Extensions.Hosting;
using Duende.AccessTokenManagement;
using Microsoft.Extensions.DependencyInjection;
using Fhi.Authentication;
using Fhi.Authentication.ClientCredentials;
using Fhi.Authentication.OpenIdConnect;
using Refit;
async Task RunAsync()
{
var builder = Host.CreateDefaultBuilder();
builder.ConfigureServices(services =>
{
var authority = "https://demo.duendesoftware.com";
var clientId = "m2m.jwt";
var privateJwk = """
{
"d":"GmiaucNIzdvsEzGjZjd43SDToy1pz-Ph-shsOUXXh-dsYNGftITGerp8bO1iryXh_zUEo8oDK3r1y4klTonQ6bLsWw4ogjLPmL3yiqsoSjJa1G2Ymh_RY_sFZLLXAcrmpbzdWIAkgkHSZTaliL6g57vA7gxvd8L4s82wgGer_JmURI0ECbaCg98JVS0Srtf9GeTRHoX4foLWKc1Vq6NHthzqRMLZe-aRBNU9IMvXNd7kCcIbHCM3GTD_8cFj135nBPP2HOgC_ZXI1txsEf-djqJj8W5vaM7ViKU28IDv1gZGH3CatoysYx6jv1XJVvb2PH8RbFKbJmeyUm3Wvo-rgQ",
"dp":"YNjVBTCIwZD65WCht5ve06vnBLP_Po1NtL_4lkholmPzJ5jbLYBU8f5foNp8DVJBdFQW7wcLmx85-NC5Pl1ZeyA-Ecbw4fDraa5Z4wUKlF0LT6VV79rfOF19y8kwf6MigyrDqMLcH_CRnRGg5NfDsijlZXffINGuxg6wWzhiqqE",
"dq":"LfMDQbvTFNngkZjKkN2CBh5_MBG6Yrmfy4kWA8IC2HQqID5FtreiY2MTAwoDcoINfh3S5CItpuq94tlB2t-VUv8wunhbngHiB5xUprwGAAnwJ3DL39D2m43i_3YP-UO1TgZQUAOh7Jrd4foatpatTvBtY3F1DrCrUKE5Kkn770M",
"e":"AQAB",
"kid":"ZzAjSnraU3bkWGnnAqLapYGpTyNfLbjbzgAPbbW2GEA",
"kty":"RSA",
"n":"wWwQFtSzeRjjerpEM5Rmqz_DsNaZ9S1Bw6UbZkDLowuuTCjBWUax0vBMMxdy6XjEEK4Oq9lKMvx9JzjmeJf1knoqSNrox3Ka0rnxXpNAz6sATvme8p9mTXyp0cX4lF4U2J54xa2_S9NF5QWvpXvBeC4GAJx7QaSw4zrUkrc6XyaAiFnLhQEwKJCwUw4NOqIuYvYp_IXhw-5Ti_icDlZS-282PcccnBeOcX7vc21pozibIdmZJKqXNsL1Ibx5Nkx1F1jLnekJAmdaACDjYRLL_6n3W4wUp19UvzB1lGtXcJKLLkqB6YDiZNu16OSiSprfmrRXvYmvD8m6Fnl5aetgKw",
"p":"7enorp9Pm9XSHaCvQyENcvdU99WCPbnp8vc0KnY_0g9UdX4ZDH07JwKu6DQEwfmUA1qspC-e_KFWTl3x0-I2eJRnHjLOoLrTjrVSBRhBMGEH5PvtZTTThnIY2LReH-6EhceGvcsJ_MhNDUEZLykiH1OnKhmRuvSdhi8oiETqtPE",
"q":"0CBLGi_kRPLqI8yfVkpBbA9zkCAshgrWWn9hsq6a7Zl2LcLaLBRUxH0q1jWnXgeJh9o5v8sYGXwhbrmuypw7kJ0uA3OgEzSsNvX5Ay3R9sNel-3Mqm8Me5OfWWvmTEBOci8RwHstdR-7b9ZT13jk-dsZI7OlV_uBja1ny9Nz9ts",
"qi":"pG6J4dcUDrDndMxa-ee1yG4KjZqqyCQcmPAfqklI2LmnpRIjcK78scclvpboI3JQyg6RCEKVMwAhVtQM6cBcIO3JrHgqeYDblp5wXHjto70HVW6Z8kBruNx1AH9E8LzNvSRL-JVTFzBkJuNgzKQfD0G77tQRgJ-Ri7qu3_9o1M4"
}
""";
var scope = "api";
services.AddInMemoryDiscoveryService([authority]);
services.AddTransient<IClientAssertionService, ClientCredentialsAssertionService>();
services.AddDistributedMemoryCache();
services.AddClientCredentialsTokenManagement();
services
.AddOptions<ClientCredentialsClient>("m2mTokenClient")
.Configure<IDiscoveryDocumentStore>((options, discoveryStore) =>
{
var discoveryDocument = discoveryStore.Get(authority);
options.TokenEndpoint = discoveryDocument?.TokenEndpoint is not null ? new Uri(discoveryDocument.TokenEndpoint) : null;
options.ClientId = ClientId.Parse(clientId);
options.Scope = Scope.Parse(scope);
options.Parameters = new ClientCredentialParametersBuilder()
.AddIssuer(discoveryDocument.Issuer)
.AddPrivateJwk(privateJwk)
.Build();
});
services.AddClientCredentialsHttpClient("m2mHttpClient", ClientCredentialsClientName.Parse("m2mTokenClient"), client =>
{
client.BaseAddress = new Uri("https://localhost:7150");
})
.AddTypedClient(RestService.For<IHealthRecordApi>);
services.AddTransient<ITestService, TestService>();
});
var host = builder.Build();
using var scope = host.Services.CreateScope();
var testService = scope.ServiceProvider.GetRequiredService<ITestService>();
await testService.Get();
}
interface ITestService
{
public Task Get();
}
// Using Refit to define the API contract
public record HealthRecordDto(string Name, string Description, DateTime CreatedAt);
public interface IHealthRecordApi
{
[Refit.Get("/api/v1/integration/health-records")]
Task<IEnumerable<HealthRecordDto>> GetHealthRecordsAsync();
}
// A service that uses the registerd RefitClient to make requests.
class TestService : ITestService
{
private readonly IHealthRecordApi _healthRecordApi;
public TestService(IHealthRecordApi healthRecordApi)
{
_healthRecordApi = healthRecordApi;
}
public async Task Get()
{
var response = await _healthRecordApi.GetHealthRecordsAsync();
Console.WriteLine($"Health Records: {response}");
foreach (var record in response)
{
Console.WriteLine($"- {record.Name}: {record.Description} (Created at: {record.CreatedAt})");
}
}
}
await RunAsync();
#!csharp
#r "nuget: Duende.AccessTokenManagement, 4.0.1"
#r "nuget: Microsoft.Extensions.Hosting, 9.0.8"
#r "nuget: Microsoft.Extensions.Http, 9.0.0"
#r "nuget: Microsoft.Extensions.Caching.Memory, 9.0.5"
#r "nuget: Fhi.Authentication.Extensions, 3.0.0"
#r "nuget: Refit, 8.0.0"
#r "nuget: Refit.HttpClientFactory, 8.0.0"
using Microsoft.Extensions.Hosting;
using Duende.AccessTokenManagement;
using Microsoft.Extensions.DependencyInjection;
using Fhi.Authentication;
using Fhi.Authentication.ClientCredentials;
using Fhi.Authentication.OpenIdConnect;
using Refit;
async Task RunAsync()
{
var builder = Host.CreateDefaultBuilder();
builder.ConfigureServices(services =>
{
var authority = "https://demo.duendesoftware.com";
var clientId = "m2m.jwt";
var privateJwk = """
{
"d":"GmiaucNIzdvsEzGjZjd43SDToy1pz-Ph-shsOUXXh-dsYNGftITGerp8bO1iryXh_zUEo8oDK3r1y4klTonQ6bLsWw4ogjLPmL3yiqsoSjJa1G2Ymh_RY_sFZLLXAcrmpbzdWIAkgkHSZTaliL6g57vA7gxvd8L4s82wgGer_JmURI0ECbaCg98JVS0Srtf9GeTRHoX4foLWKc1Vq6NHthzqRMLZe-aRBNU9IMvXNd7kCcIbHCM3GTD_8cFj135nBPP2HOgC_ZXI1txsEf-djqJj8W5vaM7ViKU28IDv1gZGH3CatoysYx6jv1XJVvb2PH8RbFKbJmeyUm3Wvo-rgQ",
"dp":"YNjVBTCIwZD65WCht5ve06vnBLP_Po1NtL_4lkholmPzJ5jbLYBU8f5foNp8DVJBdFQW7wcLmx85-NC5Pl1ZeyA-Ecbw4fDraa5Z4wUKlF0LT6VV79rfOF19y8kwf6MigyrDqMLcH_CRnRGg5NfDsijlZXffINGuxg6wWzhiqqE",
"dq":"LfMDQbvTFNngkZjKkN2CBh5_MBG6Yrmfy4kWA8IC2HQqID5FtreiY2MTAwoDcoINfh3S5CItpuq94tlB2t-VUv8wunhbngHiB5xUprwGAAnwJ3DL39D2m43i_3YP-UO1TgZQUAOh7Jrd4foatpatTvBtY3F1DrCrUKE5Kkn770M",
"e":"AQAB",
"kid":"ZzAjSnraU3bkWGnnAqLapYGpTyNfLbjbzgAPbbW2GEA",
"kty":"RSA",
"n":"wWwQFtSzeRjjerpEM5Rmqz_DsNaZ9S1Bw6UbZkDLowuuTCjBWUax0vBMMxdy6XjEEK4Oq9lKMvx9JzjmeJf1knoqSNrox3Ka0rnxXpNAz6sATvme8p9mTXyp0cX4lF4U2J54xa2_S9NF5QWvpXvBeC4GAJx7QaSw4zrUkrc6XyaAiFnLhQEwKJCwUw4NOqIuYvYp_IXhw-5Ti_icDlZS-282PcccnBeOcX7vc21pozibIdmZJKqXNsL1Ibx5Nkx1F1jLnekJAmdaACDjYRLL_6n3W4wUp19UvzB1lGtXcJKLLkqB6YDiZNu16OSiSprfmrRXvYmvD8m6Fnl5aetgKw",
"p":"7enorp9Pm9XSHaCvQyENcvdU99WCPbnp8vc0KnY_0g9UdX4ZDH07JwKu6DQEwfmUA1qspC-e_KFWTl3x0-I2eJRnHjLOoLrTjrVSBRhBMGEH5PvtZTTThnIY2LReH-6EhceGvcsJ_MhNDUEZLykiH1OnKhmRuvSdhi8oiETqtPE",
"q":"0CBLGi_kRPLqI8yfVkpBbA9zkCAshgrWWn9hsq6a7Zl2LcLaLBRUxH0q1jWnXgeJh9o5v8sYGXwhbrmuypw7kJ0uA3OgEzSsNvX5Ay3R9sNel-3Mqm8Me5OfWWvmTEBOci8RwHstdR-7b9ZT13jk-dsZI7OlV_uBja1ny9Nz9ts",
"qi":"pG6J4dcUDrDndMxa-ee1yG4KjZqqyCQcmPAfqklI2LmnpRIjcK78scclvpboI3JQyg6RCEKVMwAhVtQM6cBcIO3JrHgqeYDblp5wXHjto70HVW6Z8kBruNx1AH9E8LzNvSRL-JVTFzBkJuNgzKQfD0G77tQRgJ-Ri7qu3_9o1M4"
}
""";
var scope = "api";
services.AddInMemoryDiscoveryService([authority]);
services.AddTransient();
services.AddDistributedMemoryCache();
services.AddClientCredentialsTokenManagement();
services
.AddOptions("m2mTokenClient")
.Configure((options, discoveryStore) =>
{
var discoveryDocument = discoveryStore.Get(authority);
options.TokenEndpoint = discoveryDocument?.TokenEndpoint is not null ? new Uri(discoveryDocument.TokenEndpoint) : null;
options.ClientId = ClientId.Parse(clientId);
options.Scope = Scope.Parse(scope);
options.Parameters = new ClientCredentialParametersBuilder()
.AddIssuer(discoveryDocument.Issuer)
.AddPrivateJwk(privateJwk)
.Build();
});
services.AddClientCredentialsHttpClient("m2mHttpClient", ClientCredentialsClientName.Parse("m2mTokenClient"), client =>
{
client.BaseAddress = new Uri("https://localhost:7150");
})
.AddTypedClient(RestService.For);
services.AddTransient();
});
var host = builder.Build();
using var scope = host.Services.CreateScope();
var testService = scope.ServiceProvider.GetRequiredService();
await testService.Get();
}
interface ITestService
{
public Task Get();
}
// Using Refit to define the API contract
public record HealthRecordDto(string Name, string Description, DateTime CreatedAt);
public interface IHealthRecordApi
{
[Refit.Get("/api/v1/integration/health-records")]
Task> GetHealthRecordsAsync();
}
// A service that uses the registerd RefitClient to make requests.
class TestService : ITestService
{
private readonly IHealthRecordApi _healthRecordApi;
public TestService(IHealthRecordApi healthRecordApi)
{
_healthRecordApi = healthRecordApi;
}
public async Task Get()
{
var response = await _healthRecordApi.GetHealthRecordsAsync();
Console.WriteLine($"Health Records: {response}");
foreach (var record in response)
{
Console.WriteLine($"- {record.Name}: {record.Description} (Created at: {record.CreatedAt})");
}
}
}
await RunAsync();
In [ ]:
Copied!