Logo
  • Home
  • Overview packages
  • Concepts and terminology

Tutorials

  • Require authentication by default
  • Handling token expiration
  • Security middlewares overview
  • Server to server communication (M2M)
    • Overview
    • Client credentials token request from a Web host
    • Manual client credential token request
    • Service calling multiple external APIs

Code lab

  • Client credentials flow (server to server)
    • Using IHttpClientFactory and Duende AccessToken Management
      • Steps
      • Sample using HttpClient
      • Sample using Refit
    • Bearer token request using client assertion
    • Bearer token request using shared secret
    • DPoP token request
    • Manual token request with Duende IdentityModel
FHI authentication and authorization extensions
  • Code lab
  • Client credentials flow (server to server)
  • Using IHttpClientFactory and Duende AccessToken Management
  • Edit on GitHub

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¶

  1. Start the API using the WebAPI sample.
  2. Run the code below with .NET Interactive in VS Code or Jupyter Lab. You have the following options:
    1. Sample using HttpClient
    2. 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!

Previous Next

Built with MkDocs using a theme provided by Read the Docs.
GitHub « Previous Next »