Hasan Darwish
Hasan Darwish

Reputation: 435

Can't get access token from Identity Server 4 when using IHttpClientFactory and Intercept HttpClient GetAsync

I am trying to get access token from Identity Server 4 in my .Net 6 solution. I have 3 projects are running : Identity Server to get access token from it Movies.API to access secured resource Movies.Client to calling the api and get the secured resource using the access token that I got it from Identity Server using http message handler

here are my code for identity server config (related Clients):

public class Config
{
    public static IEnumerable<Client> Clients =>
        new Client[]
        {
            new Client
               {
                    ClientId = "movieClient",
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes = { "movieAPI" } // as the name in ApiScopes static list 
               },
            new Client
               {
                   ClientId = "movies_mvc_client",
                   ClientName = "Movies MVC Web App",
                   AllowedGrantTypes = GrantTypes.Code,
                   RequirePkce = false,
                   AllowRememberConsent = false,
                   RedirectUris = new List<string>()
                   {
                       "https://localhost:5002/signin-oidc"
                   },
                   PostLogoutRedirectUris = new List<string>()
                   {
                       "https://localhost:5002/signout-callback-oidc"
                   },
                   ClientSecrets = new List<Secret>
                   {
                       new Secret("secret".Sha256())
                   },
                   AllowedScopes = new List<string>
                   {
                       IdentityServerConstants.StandardScopes.OpenId,
                       IdentityServerConstants.StandardScopes.Profile,

                   }
            }
        };



    public static IEnumerable<ApiScope> ApiScopes =>
       new ApiScope[]
       {
           new ApiScope("movieAPI","Movie API")
       };

// rest of the code ...

and here are the injected IHttpClientFactory in the service GetMovies() :

public async Task<IEnumerable<Movie>> GetMovies()
    {
        // ** OPTION 1 **
        var httpClient = _httpClientFactory.CreateClient("MovieAPIClient");
    
        var request = new HttpRequestMessage(HttpMethod.Get, "/api/movies/");

        // using the handler
        var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
    
        response.EnsureSuccessStatusCode();
    
        var content = await response.Content.ReadAsStringAsync();
    
        var movieList = JsonConvert.DeserializeObject<List<Movie>>(content);
    
        return movieList;
        ...

and here are the implementation of the request handler : enter image description here

and finally here are the registration of the HttpClient in Movie.Client Program.cs :

// 1- create an httpclient used for accessing in Movies.API
builder.Services.AddTransient<AuthenticationDelegatingHandler>();

builder.Services.AddHttpClient("MovieAPIClient", client =>
{
    client.BaseAddress = new Uri("https://localhost:5001/");
    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
}).AddHttpMessageHandler<AuthenticationDelegatingHandler>();


// 2- create an httpclient used for accessing in the IDP
builder.Services.AddHttpClient("IDPClient", client =>
{
    client.BaseAddress = new Uri("https://localhost:5005/");
    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
}).AddHttpMessageHandler<AuthenticationDelegatingHandler>();


builder.Services.AddSingleton(new ClientCredentialsTokenRequest
{
    Address = "https://localhost:5005/connect/token",
    ClientId = "movieClient",
    ClientSecret = "secret",
    Scope = "movieAPI"
});

now in the debugging : every time I hit continue button I get back again to SendAsync() in the handler.

Could anyone give me a hand to fix this issue, Thank you.

Upvotes: 0

Views: 123

Answers (2)

Hasan Darwish
Hasan Darwish

Reputation: 435

I find the problem !, it is because I am using the handler on "IDPClient" which it should be handled by Identity Server 4 side.

before

    // 2- create an httpclient used for accessing in the IDP
    builder.Services.AddHttpClient("IDPClient", client =>
    {
        client.BaseAddress = new Uri("https://localhost:5005/");
        client.DefaultRequestHeaders.Clear();
        client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    }).AddHttpMessageHandler<AuthenticationDelegatingHandler>(); // remove the handler

after

   // 2- create an httpclient used for accessing in the IDP
   builder.Services.AddHttpClient("IDPClient", client =>
   {
        client.BaseAddress = new Uri("https://localhost:5005/");
        client.DefaultRequestHeaders.Clear();
        client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
   });

and it will works!

Upvotes: 0

Aymard Moyo Kemgne
Aymard Moyo Kemgne

Reputation: 3

The problem you faced is an infinite loop because you're using the same httpClient for your request to get movies and your request to get accessToken.

Each request using the httpClient will go through AuthenticationDelegatingHandler.

To fix your problem, that's my solution.

  1. Update the request handler
public class AuthenticationDelegatingHandler : DelegatingHandler{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly ClientCredentialsTokenRequest _tokenRequest;

    public AuthenticationDelegatingHandler(IHttpClientFactory httpClientFactory, ClientCredentialsTokenRequest tokenRequest){
        _httpClientFactory = httpClientFactory;
        _tokenRequest = tokenRequest;
    }

    protected override async Task<IHttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){
        // HttpClientFactory will create a new httpClient different from the one registered in Program.cs
        var httpClient =  _httpClientFactory.CreateClient();

        var tokenResponse = await httpClient.RequestClientCredentialsTokenAsync(_tokenRequest);
        if(tokenResponse.IsError){
            throw new HttpRequestException("Something went wrong while requesting the access token");
        }

        request.SetBearerToken(tokenResponse.AccessToken);

        return await base.SendAsync(request, cancellationToken);
    }
}

Upvotes: 0

Related Questions