Refit - How to have OPTIONAL dynamic headers - c#

I am using Refit and would like to set OPTIONAL dynamic headers for some methods. For example if the user is logged in, I want to have header "UserId" and "AuthenticationToken", otherwise do NOT set the headers
[Post("/search")]
Task<SearchResponse> Search([Body]SearchRequest request, [Header("UserId")] string userId,[Header("AuthorizationToken")] string token);
Not sure if I pass null value to userId and token, the two headers will have null value or just be skipped (not included in the header)?
Thanks.

Using refit you can implement DelegatingHandler and then register that to do whatever you need to the http request before it is sent on. Here is adding an origin header to each request. Refit interface does not need to worry about it.
public class AddOriginHeaderToRequest : DelegatingHandler
{
private const string ServiceNameSettingLocation = "AppConfig:Logging:ServiceName";
private readonly IHttpContextAccessor httpContextAccessor;
private readonly IConfiguration configuration;
public AddOriginHeaderToRequest(IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
{
this.httpContextAccessor = httpContextAccessor;
this.configuration = configuration;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var origin = this.configuration[AddOriginHeaderToRequest.SomethingThatShouldBeDefined];
if (!(request.Headers.Contains("origin") || request.Headers.Contains("Origin")) && origin != null)
{
request.Headers.Add("origin", origin);
}
return await base.SendAsync(request, cancellationToken);
}
}
Then register it like this:
services.AddTransient<AddOriginHeaderToRequest>();
Then the refit client can be registered like this (this is a redacted version of one of our nuget packages so will hopefully give an idea of how it works):
public static IHttpClientBuilder AddHttpClientWithDefaultHandlers(
this IServiceCollection services,
string name,
Action<HttpClient> configureClient)
{
return services.AddHttpClient(name, configureClient)
.AddHttpMessageHandler<AddOriginHeaderToRequest>();
}
Then in our service we register our refit handler like this:
services.AddHttpClientWithRefitAndDefaultHandlers<ImyHttpClient>(
"myHttpClient",
c =>
{
c.BaseAddress = new Uri(appSettings.Value.AppConfig.SomeUrl);
});
This can be simplified but we have a number of different handlers that massage our http requests in a standard way.
I hope that gives you a pointer to how it could work.

Related

C# gRPC client interceptor set Authorization header

I'm trying to create an interceptor for the gRPC client that the configured API token is always set.
The problem is that I just can't find a way to set the context.Options.Headers. If I'm reading the docs I need to call the WithHeaders method and that needs to set the new MetaData so I can add more headers. The only problem is that still doesn't create the headers object.
Does anyone know what I'm doing wrong?
Docs:
https://grpc.github.io/grpc/csharp/api/Grpc.Core.CallOptions.html?q=calloptions
Code:
using System;
using Floof.Common.Exceptions.IoC;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Floof.Common.GrpcClient.Interceptors
{
public class AuthorizationHeaderInterceptor : Interceptor
{
private readonly ILogger<AuthorizationHeaderInterceptor> _logger;
public const string Section = "gRPC";
public const string Key = "ApiKey";
private const string AuthorizationHeader = "Authorization";
private readonly string _apiToken;
public AuthorizationHeaderInterceptor(
ILogger<AuthorizationHeaderInterceptor> logger,
IConfiguration configuration
)
{
if (configuration == null)
throw new ArgumentNullException(nameof(configuration));
_logger = logger;
var apiToken = configuration.GetSection(Section)?[Key];
if (string.IsNullOrWhiteSpace(apiToken))
throw new IoCConfigurationException("API key", Section, Key);
_apiToken = apiToken;
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation
)
{
// Check if the headers are not null, if so, initialize new headers
if (context.Options.Headers == null)
{
_logger.LogDebug("Adding gRPC option headers");
context.Options.WithHeaders(new Metadata());
}
// gRPC calls and responses can also include metadata that's similar to HTTP headers. This metadata is mostly
// invisible to gRPC itself and is passed through to be processed by your application code or middleware.
// Metadata is represented as key/value pairs, where the key is a string and the value is either a string or
// binary data. You don’t need to specify metadata in the .proto file.
// https://learn.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/metadata
var authorization = new Metadata.Entry(AuthorizationHeader, _apiToken);
// Check if the header already has an Authorization header within. For now we agreed on that no one is allowed
// to set this header. If we want to use this client for external API services as well, we need to look up if
// this is a good client to use and what changes it takes for this client to pair with another API then the Floof cloud
var setAuthorizationHeaderEntry = context.Options.Headers.Get(AuthorizationHeader);
if (setAuthorizationHeaderEntry != null)
{
_logger.LogWarning("Replacing the Authorization header by the configured Floof API key value.");
// Remove the header out of the options because we set the configured key
context.Options.Headers.Remove(setAuthorizationHeaderEntry);
}
// Add the header to the context.
context.Options.Headers.Add(authorization);
// replace the context with the authorization context
return base.AsyncUnaryCall(request, context, continuation);
}
}
}
Trying to pass new context new ClientInterceptorContext with all needed headers set when calling return base.AsyncUnaryCall should help:
public class AuthorizationHeaderInterceptor : Interceptor
{
...
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
...
var headers = new Metadata();
headers.Add(new Metadata.Entry("Authorization", _token));
var newOptions = context.Options.WithHeaders(headers);
var newContext = new ClientInterceptorContext<TRequest, TResponse>(
context.Method,
context.Host,
newOptions);
return base.AsyncUnaryCall(request, newContext, continuation);
}
}
https://github.com/grpc/grpc-dotnet/issues/1255#issuecomment-811635583

HeaderHandler by AuthorizationHandler : No authenticationScheme was specified, and there was no DefaultChallengeScheme found

I'm trying to handle Headers obbligatoriety for an asp.net core web api proj using AuthorizationHandler
I wrote my Handler and Requirement classes:
Handler class:
public class HttpHeaderHandler : AuthorizationHandler<HttpHeaderRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor = null;
private readonly ILogger<HttpHeaderHandler> _logger;
public HttpHeaderHandler(IHttpContextAccessor httpContextAccessor, ILogger<HttpHeaderHandler> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
HttpHeaderRequirement requirement)
{
if (_httpContextAccessor?.HttpContext?.Request?.Headers != null &&
!string.IsNullOrEmpty(_httpContextAccessor.HttpContext.Request.Headers[requirement.Header]))
{
context.Succeed(requirement);
}
else
{
context.Fail();
_logger.LogWarning($"Policy validation for header {requirement.Header} failed");
}
return Task.CompletedTask;
}
}
Requirement class
public class HttpHeaderRequirement : IAuthorizationRequirement
{
public HttpHeaderRequirement(string header)
{
Header = header;
}
public string Header { get; }
}
So I added it into the ConfigurationServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("HttpHeaderRequirement",
policy => policy.Requirements.Add(new SRN.Microservice.Commons.Authorization.HttpHeaderRequirement("Auth_Token")));
});
}
so I've created my Controller class using the Authorization implemented:
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = "HttpHeaderRequirement")]
public class MyController : ControllerBase
{
[....]
}
When I try to call my API the server returns the following exception:
System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Is anyone able to help me?
I think you might be mis-using the authorization system in this case if you are not interested in an authenticated user.
Authorization is designed to say "if you require authorisation, you will be challenged to provide authentication details" after which it can run the handlers. In your case, you have not configured any authentication (because you are not actually checking the logged in user?).
In your case, what do you want to happen? If you simply want to return an error page, you could write your own attribute that is nothing to do with built-in authorization and runs on the action to return the error page based on your existing logic.
If you instead intend that the missing header does require authentication, then you will need to add and configure authentication.
Edit
See https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0#action-filters for details of action filters

Add HTTP message handler to all HTTP clients

I have an HTTP message handler named AddHeadersHandler, which extends System.Net.Http.DelegatingHandler and I need it to be added to all current and future HttpClient instances, including typed, named and non-named clients.
I know I can add a handler using .AddHttpMessageHandler<AddHeadersHandler>() for a specific client, but how do I add it to all clients?
// AddHeadersHandler.cs
public class AddHeadersHandler: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.TryAddWithoutValidation("X-Correlation-Id", Guid.NewGuid.ToString());
return base.SendAsync(request, cancellationToken);
}
}
// Startup.cs
services
.AddHttpContextAccessor()
.AddTransient<AddHeadersHandler>();
services
.AddHttpClient<MyClient>()
.AddHttpMessageHandler<AddHeadersHandler>(); // I don't want to specify this for each client.
// MyClient.cs
public class MyClient
{
public HttpClient HttpClient { get; }
public MyClient(HttpClient httpClient)
{
HttpClient = httpClient;
}
public async Task GetTest()
{
await HttpClient.GetAsync("https://localhost:5001/test"); // This should have headers attached.
}
}
It can be done by configuring HttpClientFactoryOptions for all named options. We need to provide a delegate in HttpMessageHandlerBuilderActions, which will include your handler to AdditionalHandlers property list.
There are multiple ways of doing this using the Options pattern.
1. Using .AddSingleton() ❌
If your handler has any dependencies (ex. IHttpContextAccessor to get current correlation id), we would like to use dependency injection to resolve it.
We could use OptionsBuilder API to get a required handler using dependency injection. Unfortunately, OptionsBuilder API does not provide a method to configure options for all named instances like .ConfigureAll does.
Luckily, we can get what we need by registering a factory method for IConfigureOptions<HttpClientFactoryOptions> like so:
// Startup.cs
services.AddSingleton<IConfigureOptions<HttpClientFactoryOptions>>(provider =>
{
// When name is null, it will be used for all configurations.
return new ConfigureNamedOptions<HttpClientFactoryOptions>(name: null, options =>
{
options.HttpMessageHandlerBuilderActions.Add(builder =>
{
// Here we have access to ServiceProvider to get an instance of the handler.
builder.AdditionalHandlers.Add(provider.GetRequiredService<AddHeadersHandler>());
});
});
});
2. Using .ConfigureAll() ✔️
The following improved answer was inspired by LostInComputer.
Add .ConfigureAll in your Startup.cs and use IServiceProvider through builder object like so:
services.ConfigureAll<HttpClientFactoryOptions>(options =>
{
options.HttpMessageHandlerBuilderActions.Add(builder =>
{
builder.AdditionalHandlers.Add(builder.Services.GetRequiredService<AddHeadersHandler>());
});
});

Share HttpClient between services

I am working on a Blazor project, and to make the question I have easier to understand, we can say that I am using two different services that handles the Authentication part. Those are registered in the configureservices startup method together with a named httpclient.
services.AddHttpClient("XBOWServicesApi", c =>
{
c.BaseAddress = new Uri(XBOWServicesApi);
});
services.AddSingleton<IService1, Service1>();
services.AddSingleton<IService2, Service2>();
Service 1: Wraps all functionality available in a REST Api. It uses an http client which is set in the constructor via an instanciated httpclientfactory. This needs to be set with a baseurl and an Auth-header to work.
public Service1(IHttpClientFactory clientFactory)
{
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
Service 2: Handles the login/logout functionality using a custom AuthenticationStateProvider. It has its own httpclient, so that I can set the Auth Header for the http client. The constructor works in the same way as for Service 1.
public Service2(IHttpClientFactory clientFactory)
{
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
The reason for this build up is of course that I like to share the same http client, so when it is set in the login/logout methods, service 1 will have the correct auth header when communicating with the api.
However, the client factory provides a new instance everytime, so this will never work.
Any ideas how to handle this?
/Henrik
You can use named client:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Afterwards, just call CreateClient method with corresponding name parameter.
var client = _clientFactory.CreateClient("github");
Each time CreateClient is called:
A new instance of HttpClient is created.
The configuration action is
called.
You can find more details in Microsoft documentation here.
When I read through the Microsoft IHttpClientFactory docs:
Each time you get an HttpClient object from the IHttpClientFactory, a
new instance is returned. But each HttpClient uses an
HttpMessageHandler that's pooled and reused by the IHttpClientFactory
to reduce resource consumption, as long as the HttpMessageHandler's
lifetime hasn't expired.
Does that answer your question?
You can share scoped services between transient HttpClients by using HttpMessageHandlers.
IHttpClient.CreateClient returns a new instance every time, but you can register a HttpMessageHandler as shown below:
services.AddScoped<HandlerData>();
services.AddTransient<HeaderHandler>();
services.AddHttpClient("XBOWServicesApi", c =>
{
c.BaseAddress = new Uri(XBOWServicesApi);
}).AddHttpMessageHandler<HeaderHandler>();
HeaderHandler Class:
public class HeaderHandler : DelegatingHandler
{
private readonly IHttpContextAccessor httpContextAccessor;
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken )
{
var Data= this.httpContextAccessor.HttpContext.RequestServices.GetRequiredService<HandlerData>();
request.Headers.Add(Data.HeaderName, Data.HeaderValue);
return base.SendAsync(request, cancellationToken);
}
}
HandlerData Class:
public class HandlerData
{
public string HeaderName { get; set; }
public string HeaderValue { get; set; }
}
ServicesCode:
public Service1(IHttpClientFactory clientFactory, HandlerData data)
{
data.HeaderName = "Header1";
data.HeaderValue = "Value";
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
public Service2(IHttpClientFactory clientFactory)
{
//This will contain the same headers as Service1 as HandlerData is Scoped Service
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
Alternatively, you can also use new IHttpMessageHandlerFactory if you need to create handlers that live in the same DI scope as you request:
Reference: https://github.com/aspnet/HttpClientFactory/issues/166

Microsoft Owin Logging - Web Api 2 - How do I create the logger?

I am trying to add logging to my app using Web Api 2 and Owin, so I started using Microsoft Owin Logging, which requires an ILogger and ILoggerFactory, that has been implemented and it works great when I need to log anything inside the STARTUP method or any of the Owin Middleware components.
For example, when I am in the Startup method I can create the logger using:
public void Configuration(IAppBuilder app)
{
// Creates configuration
var configuration = new HttpConfiguration();
// Configure WebApi Settings
WebApiConfig.Register(configuration);
app.SetLoggerFactory(new OwinLog4NetLoggerFactory("Default"));
var logger = app.CreateLogger<Startup>();
logger.WriteInformation("test log");
// Enabled WebApi in OWIN
app.UseWebApi(configuration);
}
Where "OwinLog4NetLoggerFactory" is my custom ILoggerFactory implementation.
So far, so good... but... How can I create the logger when I am in the actual web api action method?... I tried accessing the Request.GetOwinEnvironment() and the logger factory is not in the dictionary.
For example:
public class AccountController : ApiController
{
public int Get(int id)
{
// Create logger here
return id + 1;
}
}
I know I can create a static class with a reference to the Logger Factory or even Injection to add the logger to the api controller, but that seems too complicated for something that should be already there.
Any ideas would be appreciated.
I'd recommend writing your middleware so that you can handle the logging outside of the controller:
public class LoggingMiddleware : OwinMiddleware
{
public LoggingMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
//handle request logging
await Next.Invoke(context);
//handle response logging
}
}
Then in Startup class:
public class Startup
{
// ReSharper disable once UnusedMember.Global
public void Configuration(IAppBuilder appBuilder)
{
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
appBuilder.Use<LoggingMiddleware>();
appBuilder.UseWebApi(config);
}
}
The request would then come in, hit the request logging code in the LoggingMiddleware, hit the controller code and then response would be logged on the LoggingMiddleware on the way back.
However, if all you are wanting to do is send an object through from middleware to the controller you can use context.Set("loggingObject", loggingObject); in the middleware and then
var loggingObject = Request.GetOwinContext().Get<LoggerClass>("loggingObject"); in the controller.
instead of adding logging code in every method, I create a MessageLoggingHandler that can be registered in Global.asax.cs once, and it then logs every Request and Response.
Here is the code that I use, you can modify as per your requirements:
First Create a MessageHandler class that inherits from DelegationHandler:
public abstract class MessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var corrId = Guid.NewGuid();
var requestMethod = request.Method.Method.ToString();
var requestUri = request.RequestUri.ToString();
var ipAddress = request.GetOwinContext().Request.RemoteIpAddress;
var requestMessage = await request.Content.ReadAsByteArrayAsync();
await LogMessageAsync(corrId, requestUri, ipAddress, "Request", requestMethod, request.Headers.ToString(), requestMessage, string.Empty);
var response = await base.SendAsync(request, cancellationToken);
var responseMessage = await response.Content.ReadAsByteArrayAsync();
await LogMessageAsync(corrId, requestUri, ipAddress, "Response", requestMethod, response.Headers.ToString(), responseMessage, ((int)response.StatusCode).ToString() + "-" + response.ReasonPhrase);
return response;
}
protected abstract Task LogMessageAsync(Guid CorrelationId, string APIUrl, string ClientIPAddress, string RequestResponse, string HttpMethod, string HttpHeaders, byte[] HttpMessage, string HttpStatusCode);
}
public class MessageLoggingHandler : MessageHandler
{
protected override async Task LogMessageAsync(Guid CorrelationId, string APIUrl, string ClientIPAddress, string RequestResponse, string HttpMethod, string HttpHeaders, byte[] HttpMessage, string HttpStatusCode)
{
// Create logger here
//Do your logging here
}
}
Then in your Global.asax.cs, you need to register the above created MessageLoggingHandler:
GlobalConfiguration.Configuration.MessageHandlers.Add(new MessageLoggingHandler());
Just be aware, this will log every request and response, will full message body. This can take a lot of space very quickly (depending on your API's usage). So you may need to tweak this (for example - keep records for a month or so, or ignore 200-OK responses etc)
I would recommend using the Common.Logging library in your applications, available on NuGet. Common.Logging gives you a common interface for using your preferred logging solution. It solves a lot of issues like yours. Here is an example using Common.Logging with NLog:
In your controller, you would access it like so:
public class MyController : ApiController
{
private static readonly ILog Log = LogManager.GetLogger<MyController>();
public async Task<IHttpActionResult> Get([FromUri] int id)
{
Log.Debug("Called Get with id " + id.ToString());
return Ok();
}
}
Pick up the latest Common.Logging.NLog package on NuGet (as of this writing, it should be Common.Logging.NLog41). Then in your web.config, you would configure Common.Logging to use your NLog configuration:
<common>
<logging>
<factoryAdapter type="Common.Logging.NLog.NLogLoggerFactoryAdapter, Common.Logging.NLog41">
<arg key="configType" value="FILE" />
<arg key="configFile" value="~/NLog.config" />
</factoryAdapter>
</logging>
</common>
Here are some additional links:
https://github.com/net-commons/common-logging
https://cmatskas.com/an-introduction-to-common-logging-api-2/

Categories

Resources