Open Closed

Additional details on HttpClientFactory #2823


User avatar
0
viswajwalith created
  • ABP Framework version: v5.2
  • UI type: MVC
  • DB provider: EF Core / MongoDB
  • Tiered (MVC) or Identity Server Separated (Angular): yes (Micro service)
  • Exception message and stack trace:
  • Steps to reproduce the issue:"

We would like to understand the http request flow between Web(MVC UI) Layer and WebGateway. We assume tis uses AbpAspNetCoreMvcClientModule for sending the request from Web to Gateway by means of HTTP Client Factory.

We tied to find the soucre code of module "AbpAspNetCoreMvcClientModule" but didnt found that.

Can you please provide more details on communication between WebUI & WebGateway (for http requests) and relevant code which passes Barer token etc...


5 Answer(s)
  • User Avatar
    0
    gterdem created
    Senior .NET Developer

    You may want to check these framework modules:

  • User Avatar
    0
    viswajwalith created

    You may want to check these framework modules:

    Can you please validate our understanding which I mentioned in my Question Posting.

  • User Avatar
    0
    gterdem created
    Senior .NET Developer

    We would like to understand the http request flow between Web(MVC UI) Layer and WebGateway.

    From the microservice solution perspective, WebUI (I am assuming you mean public-web application) is not a layer but an application.

    Can you please provide more details on communication between WebUI & WebGateway (for http requests) and relevant code which passes Barer token etc...

    I think you want to learn generally how the proxying works; how an HTTP request is done, headers are set and forwarded; not just microservice specific. So I will try to explain better.

    Your request is created by ClientProxyBase (or DynamicHttpProxyInterceptor if you are using dynamic proxying):

    protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceHttpClientAuthenticator>();
    
    protected virtual async Task<HttpContent> RequestAsync(ClientProxyRequestContext requestContext)
    {
        ...
        // Gets the remote service configuration
        RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName);
        
        // Creates the HttpClient service configuration
        var client = HttpClientFactory.Create(clientConfig.RemoteServiceName);
        
        ...    
        
        // Authentication headers are forwarded or set in here based on remoteServiceConfig
        if (requestContext.Action.AllowAnonymous != true)
        {
            await ClientAuthenticator.Authenticate(
                new RemoteServiceHttpClientAuthenticateContext(
                    client,
                    requestMessage,
                    remoteServiceConfig,
                    clientConfig.RemoteServiceName
                )
            );
        }
    
        ...
    }
    

    IRemoteServiceHttpClientAuthenticator is implemented by:

    await IdentityModelAuthenticationService.TryAuthenticateAsync(
                context.Client,
                context.RemoteService.GetIdentityClient() ?? context.RemoteServiceName
            );
    

    Uses IdentityModelAuthenticationService to authenticate the service as below:

    public async Task<bool> TryAuthenticateAsync(
            [NotNull] HttpClient client,
            string identityClientName = null)
    {
        var accessToken = await GetAccessTokenOrNullAsync(identityClientName);
        if (accessToken == null)
        {
            return false;
        }
    
        SetAccessToken(client, accessToken);
        return true;
    }
    protected virtual void SetAccessToken(HttpClient client, string accessToken)
    {
        //TODO: "Bearer" should be configurable
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    }
    
    public override async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context)
    {
        if (context.RemoteService.GetUseCurrentAccessToken() != false)
        {
            var accessToken = await GetAccessTokenFromHttpContextOrNullAsync();
            if (accessToken != null)
            {
                context.Request.SetBearerToken(accessToken);
                return;
            }
        }
    
        await base.Authenticate(context);
    }
    
    protected virtual async Task<string> GetAccessTokenFromHttpContextOrNullAsync()
    {
        var httpContext = HttpContextAccessor?.HttpContext;
        if (httpContext == null)
        {
            return null;
        }
    
        return await httpContext.GetTokenAsync("access_token");
    }
    

    Basically, how to set the authorization header is based on application type;

    • Server-to-server communication without HTTP context uses Volo.Abp.Http.Client.IdentityModel module
    • MVC/Razor application uses Volo.Abp.Http.Client.IdentityModel.Web module
    • Blazor application uses Volo.Abp.Http.Client.IdentityModel.WebAssembly module
  • User Avatar
    0
    viswajwalith created

    Volo.Abp.Http.Client.IdentityModel

    Thanks for the detailed explanation, Yes this is what we expected. I am going through the details will let you know in case of any queries

  • User Avatar
    0
    viswajwalith created

    We would like to understand the http request flow between Web(MVC UI) Layer and WebGateway.

    From the microservice solution perspective, WebUI (I am assuming you mean public-web application) is not a layer but an application.

    Can you please provide more details on communication between WebUI & WebGateway (for http requests) and relevant code which passes Barer token etc...

    I think you want to learn generally how the proxying works; how an HTTP request is done, headers are set and forwarded; not just microservice specific. So I will try to explain better.

    Your request is created by ClientProxyBase (or DynamicHttpProxyInterceptor if you are using dynamic proxying):

    protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceHttpClientAuthenticator>(); 
     
    protected virtual async Task<HttpContent> RequestAsync(ClientProxyRequestContext requestContext) 
    { 
        ... 
        // Gets the remote service configuration 
        RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); 
         
        // Creates the HttpClient service configuration 
        var client = HttpClientFactory.Create(clientConfig.RemoteServiceName); 
         
        ...     
         
        // Authentication headers are forwarded or set in here based on remoteServiceConfig 
        if (requestContext.Action.AllowAnonymous != true) 
        { 
            await ClientAuthenticator.Authenticate( 
                new RemoteServiceHttpClientAuthenticateContext( 
                    client, 
                    requestMessage, 
                    remoteServiceConfig, 
                    clientConfig.RemoteServiceName 
                ) 
            ); 
        } 
     
        ... 
    } 
    

    IRemoteServiceHttpClientAuthenticator is implemented by:

    await IdentityModelAuthenticationService.TryAuthenticateAsync( 
                context.Client, 
                context.RemoteService.GetIdentityClient() ?? context.RemoteServiceName 
            ); 
    

    Uses IdentityModelAuthenticationService to authenticate the service as below:

    public async Task<bool> TryAuthenticateAsync( 
            [NotNull] HttpClient client, 
            string identityClientName = null) 
    { 
        var accessToken = await GetAccessTokenOrNullAsync(identityClientName); 
        if (accessToken == null) 
        { 
            return false; 
        } 
     
        SetAccessToken(client, accessToken); 
        return true; 
    } 
    protected virtual void SetAccessToken(HttpClient client, string accessToken) 
    { 
        //TODO: "Bearer" should be configurable 
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); 
    } 
    
    public override async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context) 
    { 
        if (context.RemoteService.GetUseCurrentAccessToken() != false) 
        { 
            var accessToken = await GetAccessTokenFromHttpContextOrNullAsync(); 
            if (accessToken != null) 
            { 
                context.Request.SetBearerToken(accessToken); 
                return; 
            } 
        } 
     
        await base.Authenticate(context); 
    } 
     
    protected virtual async Task<string> GetAccessTokenFromHttpContextOrNullAsync() 
    { 
        var httpContext = HttpContextAccessor?.HttpContext; 
        if (httpContext == null) 
        { 
            return null; 
        } 
     
        return await httpContext.GetTokenAsync("access_token"); 
    } 
    

    Basically, how to set the authorization header is based on application type;

    • Server-to-server communication without HTTP context uses Volo.Abp.Http.Client.IdentityModel module
    • MVC/Razor application uses Volo.Abp.Http.Client.IdentityModel.Web module
    • Blazor application uses Volo.Abp.Http.Client.IdentityModel.WebAssembly module

    Thanks for the details, now we are able to over ride the specific method to add additional headers (in our sample solution) . Thanks for the support

Made with ❤️ on ABP v9.1.0-preview. Updated on November 11, 2024, 11:11