none
Interceptar peticiones para agregar Header de autorización RRS feed

  • Pregunta

  • Buenas,

    Decidí crear otra pregunta relacionada a ésta que ya me respondieron.

    ¿Cómo podría hacer para centralizar en un sólo lugar el mecanismo para agregar el token y no tener que validar la expiración del mismo en todos los lugares donde se utilice un objeto HttpClient?

    Actualmente he agregado los objetos HttpClient al contenedor DI de la siguiente manera:

    public void ConfigureServices(IServiceCollection services)
    {
    	var baseAddress = "https://localhost:5001/";	// Identity Server
    	services.AddHttpClient("IdentityServerClient", client => {
    		client.BaseAddress = new Uri(baseAddress);
    		client.DefaultRequestHeaders.Clear();
    		client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    	});
    
    	baseAddress = "https://localhost:5002/";	// Api Gateway
    	services.AddHttpClient("ApiGatewayClient", client => {
    		client.BaseAddress = new Uri(baseAddress);
    		client.DefaultRequestHeaders.Clear();
    		client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    	});
    	
    	baseAddress = "https://localhost:5003/";	// Api Common
    	services.AddHttpClient("ApiCommonClient", client => {
    		client.BaseAddress = new Uri(baseAddress);
    		client.DefaultRequestHeaders.Clear();
    		client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    	});
    	
    	// ...
    }

    Y en los controladores inyecto un IHttpClientFactory vía contructor para obtener los diferentes objetos HttpClient según necesite.

    var httpClient = httpClientFactory.CreateClient("ApiGatewayClient");
    var response = await httpClient.GetAsync("main/api/common/Countries");

    Imagino que debe existir algún evento que se dispara cuando se hace el Request a la Api y en ese momento poder validar la expiración del token y así poder refrescarlo.

    Espero dejarme comprender.

    Muchas gracias.


    • Editado Ealdaz sábado, 2 de enero de 2021 22:57
    sábado, 2 de enero de 2021 22:55

Respuestas

  • Hola, puedes establecer un DelegatingHandler a tu cliente. Esto es un ejemplo


    public class RefreshTokenDelegatingHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var token = await GetToken();
            request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
            var response = await base.SendAsync(request, cancellationToken);
    
            if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
            {
                token = await RefreshToken();
                request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
                response = await base.SendAsync(request, cancellationToken);
            }
    
            return response;
        }
    }
    Una vez creado lo asignas a tu cliente
    baseAddress = "https://localhost:5003/";	// Api Common
    	services.AddHttpClient("ApiCommonClient", client => {
    		client.BaseAddress = new Uri(baseAddress);
    		client.DefaultRequestHeaders.Clear();
    		client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    	}).AddHttpMessageHandler<RefreshTokenDelegatingHandler>()



    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos

    • Marcado como respuesta Ealdaz domingo, 3 de enero de 2021 17:49
    domingo, 3 de enero de 2021 12:55
    Moderador
  • Hola Sergio,

    Escribí el Handler de la siguiente manera:

    public class RefreshTokenDelegatingHandler : DelegatingHandler
    {
    	private readonly IHttpContextAccessor httpContextAccessor;
    	private readonly IConfiguration configuration;
    	private readonly IHttpClientFactory httpClientFactory;
    
    	public RefreshTokenDelegatingHandler(IHttpContextAccessor httpContextAccessor,
    										 IConfiguration configuration,
    										 IHttpClientFactory httpClientFactory)
    	{
    		this.httpContextAccessor = httpContextAccessor;
    		this.configuration = configuration;
    		this.httpClientFactory = httpClientFactory;
    	}
    
    	protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    	{
    		var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
    		var tokenHandler = new JwtSecurityTokenHandler();
    		var jwtSecurityToken = tokenHandler.ReadJwtToken(accessToken);
    
    		// Verificar si el token está próximo a expirar (minutos antes)
    		var minutes = configuration.GetValue<int>("IS4_Settings:MinutesBeforeToExpiration");
    		if (jwtSecurityToken.ValidTo < DateTime.UtcNow.AddMinutes(minutes))
    		{
    			var tokenResponse = await RenewTokenAsync(httpContextAccessor.HttpContext);
    
    			if (tokenResponse == null)
    			{
    				throw new Exception("Error");
    			}
    
    			accessToken = tokenResponse.AccessToken;
    		}
    
    		request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    
    		return await base.SendAsync(request, cancellationToken);
    	}
    
    	public async Task<TokenResponse> RenewTokenAsync(HttpContext httpContext)
    	{
    		// Inicializar el Endpoint
    		var idpAddress = configuration.GetValue<string>("IS4_Settings:Authority");
    		var client = httpClientFactory.CreateClient();
    		var disco = await client.GetDiscoveryDocumentAsync(idpAddress);
    
    		if (disco.IsError) throw new Exception(disco.Error);
    
    		// Leer el token de actualización almacenado
    		var rt = await httpContext.GetTokenAsync("refresh_token");
    		var tokenClient = httpClientFactory.CreateClient();
    
    		var clientId = configuration.GetValue<string>("IS4_Settings:ClientId");
    		var secretKey = configuration.GetValue<string>("IS4_Settings:SecretKey");
    
    		// Solicitar un nuevo token
    		var tokenResult = await tokenClient.RequestRefreshTokenAsync(new RefreshTokenRequest
    		{
    			Address = disco.TokenEndpoint,
    			ClientId = clientId,
    			ClientSecret = secretKey,
    			RefreshToken = rt
    		});
    
    		if (!tokenResult.IsError)
    		{
    			var old_id_token = await httpContext.GetTokenAsync("id_token");
    			var new_access_token = tokenResult.AccessToken;
    			var new_refresh_token = tokenResult.RefreshToken;
    			var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
    
    			// Guardar la información en la cookie
    			var info = await httpContext.AuthenticateAsync("Cookies");
    
    			info.Properties.UpdateTokenValue("refresh_token", new_refresh_token);
    			info.Properties.UpdateTokenValue("access_token", new_access_token);
    			info.Properties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
    
    			await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, info.Principal, info.Properties);
    
    			return tokenResult;
    		}
    
    		return null;
    	}
    }

    Lo asigné al cliente:

    baseAddress = configuration.GetValue<string>("Api_Address:Gateway");
    services.AddHttpClient("ApiGatewayClient", client =>
    {
    	client.BaseAddress = new Uri(baseAddress);
    	client.DefaultRequestHeaders.Clear();
    	client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    })
    	.AddHttpMessageHandler<RefreshTokenDelegatingHandler>();

    Pero al ejecutar, me tiró error:

    'RefreshTokenDelegatingHandler' has been registered.

    Entiendo que debo registrarlo al DI, y lo hice de la siguiente manera:

    services.AddSingleton<RefreshTokenDelegatingHandler>();

    Pero me sigue tirando otro tipo de error:

    System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: WebApp.Delegates.RefreshTokenDelegatingHandler Lifetime: Singleton ImplementationType: WebApp.Delegates.RefreshTokenDelegatingHandler': Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' while attempting to activate 'WebApp.Delegates.RefreshTokenDelegatingHandler'.)'

    Creo que no puede crear el objeto.


    domingo, 3 de enero de 2021 16:13
  • Estuve investigando y veo que tenia que agregar esta línea antes de registrar el Handler.

    services.AddHttpContextAccessor();

    Muchas gracias, ya funciona :)

    domingo, 3 de enero de 2021 17:49

Todas las respuestas

  • Hola, puedes establecer un DelegatingHandler a tu cliente. Esto es un ejemplo


    public class RefreshTokenDelegatingHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var token = await GetToken();
            request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
            var response = await base.SendAsync(request, cancellationToken);
    
            if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
            {
                token = await RefreshToken();
                request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
                response = await base.SendAsync(request, cancellationToken);
            }
    
            return response;
        }
    }
    Una vez creado lo asignas a tu cliente
    baseAddress = "https://localhost:5003/";	// Api Common
    	services.AddHttpClient("ApiCommonClient", client => {
    		client.BaseAddress = new Uri(baseAddress);
    		client.DefaultRequestHeaders.Clear();
    		client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    	}).AddHttpMessageHandler<RefreshTokenDelegatingHandler>()



    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos

    • Marcado como respuesta Ealdaz domingo, 3 de enero de 2021 17:49
    domingo, 3 de enero de 2021 12:55
    Moderador
  • Hola Sergio,

    Escribí el Handler de la siguiente manera:

    public class RefreshTokenDelegatingHandler : DelegatingHandler
    {
    	private readonly IHttpContextAccessor httpContextAccessor;
    	private readonly IConfiguration configuration;
    	private readonly IHttpClientFactory httpClientFactory;
    
    	public RefreshTokenDelegatingHandler(IHttpContextAccessor httpContextAccessor,
    										 IConfiguration configuration,
    										 IHttpClientFactory httpClientFactory)
    	{
    		this.httpContextAccessor = httpContextAccessor;
    		this.configuration = configuration;
    		this.httpClientFactory = httpClientFactory;
    	}
    
    	protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    	{
    		var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
    		var tokenHandler = new JwtSecurityTokenHandler();
    		var jwtSecurityToken = tokenHandler.ReadJwtToken(accessToken);
    
    		// Verificar si el token está próximo a expirar (minutos antes)
    		var minutes = configuration.GetValue<int>("IS4_Settings:MinutesBeforeToExpiration");
    		if (jwtSecurityToken.ValidTo < DateTime.UtcNow.AddMinutes(minutes))
    		{
    			var tokenResponse = await RenewTokenAsync(httpContextAccessor.HttpContext);
    
    			if (tokenResponse == null)
    			{
    				throw new Exception("Error");
    			}
    
    			accessToken = tokenResponse.AccessToken;
    		}
    
    		request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    
    		return await base.SendAsync(request, cancellationToken);
    	}
    
    	public async Task<TokenResponse> RenewTokenAsync(HttpContext httpContext)
    	{
    		// Inicializar el Endpoint
    		var idpAddress = configuration.GetValue<string>("IS4_Settings:Authority");
    		var client = httpClientFactory.CreateClient();
    		var disco = await client.GetDiscoveryDocumentAsync(idpAddress);
    
    		if (disco.IsError) throw new Exception(disco.Error);
    
    		// Leer el token de actualización almacenado
    		var rt = await httpContext.GetTokenAsync("refresh_token");
    		var tokenClient = httpClientFactory.CreateClient();
    
    		var clientId = configuration.GetValue<string>("IS4_Settings:ClientId");
    		var secretKey = configuration.GetValue<string>("IS4_Settings:SecretKey");
    
    		// Solicitar un nuevo token
    		var tokenResult = await tokenClient.RequestRefreshTokenAsync(new RefreshTokenRequest
    		{
    			Address = disco.TokenEndpoint,
    			ClientId = clientId,
    			ClientSecret = secretKey,
    			RefreshToken = rt
    		});
    
    		if (!tokenResult.IsError)
    		{
    			var old_id_token = await httpContext.GetTokenAsync("id_token");
    			var new_access_token = tokenResult.AccessToken;
    			var new_refresh_token = tokenResult.RefreshToken;
    			var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
    
    			// Guardar la información en la cookie
    			var info = await httpContext.AuthenticateAsync("Cookies");
    
    			info.Properties.UpdateTokenValue("refresh_token", new_refresh_token);
    			info.Properties.UpdateTokenValue("access_token", new_access_token);
    			info.Properties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
    
    			await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, info.Principal, info.Properties);
    
    			return tokenResult;
    		}
    
    		return null;
    	}
    }

    Lo asigné al cliente:

    baseAddress = configuration.GetValue<string>("Api_Address:Gateway");
    services.AddHttpClient("ApiGatewayClient", client =>
    {
    	client.BaseAddress = new Uri(baseAddress);
    	client.DefaultRequestHeaders.Clear();
    	client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    })
    	.AddHttpMessageHandler<RefreshTokenDelegatingHandler>();

    Pero al ejecutar, me tiró error:

    'RefreshTokenDelegatingHandler' has been registered.

    Entiendo que debo registrarlo al DI, y lo hice de la siguiente manera:

    services.AddSingleton<RefreshTokenDelegatingHandler>();

    Pero me sigue tirando otro tipo de error:

    System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: WebApp.Delegates.RefreshTokenDelegatingHandler Lifetime: Singleton ImplementationType: WebApp.Delegates.RefreshTokenDelegatingHandler': Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' while attempting to activate 'WebApp.Delegates.RefreshTokenDelegatingHandler'.)'

    Creo que no puede crear el objeto.


    domingo, 3 de enero de 2021 16:13
  • Estuve investigando y veo que tenia que agregar esta línea antes de registrar el Handler.

    services.AddHttpContextAccessor();

    Muchas gracias, ya funciona :)

    domingo, 3 de enero de 2021 17:49
  • Hola Ealdaz,

    Gracias por confirmar que se ha encontrado una solución a la consulta realizada.

    Gracias por usar los foros de MSDN.

    Eric Ruiz

    ____________________________

    Por favor recuerde "Marcar como respuesta" las respuestas que hayan resuelto su problema, es una forma común de reconocer a aquellos que han ayudado, y hace que sea más fácil para los otros visitantes encontrar la solución más tarde.

    Si tiene algún cumplido o reclamo sobre el soporte de MSDN siéntase en la libertad de contactar MSDNFSF@microsoft.com.

    lunes, 4 de enero de 2021 15:07
    Moderador