Setup JWT from headers to be used by default in HttpClient - asp.net-core

I use .NET 6 and add HttpClient in Program.cs like this:
builder.Services.AddHttpClient<IUserClient, UserClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Clients:UserBaseUrl"]);
});
In my onion architecture when I want to create an order a request is coming to OrderService.API and to check userId is correct in IUserService from OrderService.BL I call user microservice API with the help of registered HttpClient. So the problem is that for now, I need to transfer JWT to the business logic layer via method parameters.
var createdOrder = await _orderService.Add(model.MapToDto(), HttpContext.Request.Headers["Authorization"]);
I don't like it because, for every method using the HttpClient, it's necessary to provide an extra parameter. I think maybe there is a way to set up HttpClient default authentication during the current request.
I tried to setup default request headers during HttpClient registration:
builder.Services.AddHttpClient<IUserClient, UserClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Clients:UserBaseUrl"]);
client.DefaultRequestHeaders.Add("Authorization", token); // setup token
});
but I don't know how to get JWT from HttpRequest headers there.
Additionally, I thought maybe I can set up the header for HttpClient in some additional BaseController which would be nested by any other my controller but it doesn't seem to be a great solution.
Maybe there is a way for middleware use but as I understand we handle an incoming request to OrderService and can't handle outcoming requests from HttpClient.
So would be grateful for any of your ideas!

Thanks to #Rena who provided a link to an existing similar problem: https://stackoverflow.com/a/62324677/11398810
So I created a message helper like this and it seems work for me:
public sealed class HttpClientsAuthHelper : DelegatingHandler
{
private readonly IHttpContextAccessor _accessor;
public HttpClientsAuthHelper(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = _accessor.HttpContext.Request.Headers["Authorization"].First();
request.Headers.Add("Authorization", token);
return await base.SendAsync(request, cancellationToken);
}
}
And added these lines to Program.cs:
builder.Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddTransient<HttpClientsAuthHelper>();
builder.Services.AddHttpClient<IUserClient, UserClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Clients:UserBaseUrl"]);
}).AddHttpMessageHandler<HttpClientsAuthHelper>();
builder.Services.AddHttpClient<IProductClient, ProductClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Clients:ProductBaseUrl"]);
}).AddHttpMessageHandler<HttpClientsAuthHelper>();
I'm not sure how correct such approach so I'll dive a bit into this logic later =)

Related

Generic passthrough/forwarding of form data in ApsNet.Core

I'm attempting to create a webhook to receive messages from a Twilio phone number. But instead of just needing a webhook that will modify the data and immediately return a result to Twilio, I need this webhook to pass Twilio's message into an internal API, wait for the response, and then return the result to Twilio.
Here's some generic code I came up with that I hoped would work.
public async Task<HttpResponseMessage> ReceiveAndForwardSms(HttpContent smsContent)
{
var client = new HttpClient();
HttpResponseMessage response = await client.PostAsync(Environment.GetEnvironmentVariable("requestUriBase") + "/api/SmsHandler/PostSms", smsContent);
return response;
}
The problem with this code is that Twilio immediately returns a 415 error code (Unsupported Media Type) before entering the function.
When I try to accept the "correct type" (Twilio.AspNet.Common.SmsRequest), I am unable to stuff the SmsRequest back into a form-encoded object and send it via client.PostAsync()...
Ex.:
public async Task<HttpResponseMessage> ReceiveAndForwardSms([FromForm]SmsRequest smsRequest)
{
var client = new HttpClient();
var stringContent = new StringContent(smsRequest.ToString());
HttpResponseMessage response = await client.PostAsync(Environment.GetEnvironmentVariable("requestUriBase") + "/api/SmsHandler/PostSms", stringContent);
return response;
}
Is there anything I can do to "mask" the function's accepted type or keep this first function generic?
How do I go about shoving this SmsRequest back into a "form-encoded" object so I can accept it the same way in my consuming service?
TLDR
Your options are:
Use an existing reverse proxy like NGINX, HAProxy, F5
Use YARP to add reverse proxy functionality to an ASP.NET Core project
Accept the webhook request in a controller, map the headers and data to a new HttpRequestMessage and send it to your private service, then map the response of your private service, to the response back to Twilio.
It sounds like what you're trying to build is a reverse proxy. It is very common to put a reverse proxy in front of your web application for SSL termination, caching, routing based on hostname or URL, etc.
The reverse proxy will receive the Twilio HTTP request and then forwards it to the correct private service. The private service responds which the reverse proxy forwards back to Twilio.
I would recommend using an existing reverse proxy instead of building this functionality yourself. If you really want to build it yourself, here's a sample I was able to get working:
In your reverse proxy project, add a controller as such:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
namespace ReverseProxy.Controllers;
public class SmsController : Controller
{
private static readonly HttpClient HttpClient;
private readonly ILogger<SmsController> logger;
private readonly string twilioWebhookServiceUrl;
static SmsController()
{
// don't do this in production!
var insecureHttpClientHandler = new HttpClientHandler();
insecureHttpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => true;
HttpClient = new HttpClient(insecureHttpClientHandler);
}
public SmsController(ILogger<SmsController> logger, IConfiguration configuration)
{
this.logger = logger;
twilioWebhookServiceUrl = configuration["TwilioWebhookServiceUrl"];
}
public async Task Index()
{
using var serviceRequest = new HttpRequestMessage(HttpMethod.Post, twilioWebhookServiceUrl);
foreach (var header in Request.Headers)
{
serviceRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
serviceRequest.Content = new FormUrlEncodedContent(
Request.Form.ToDictionary(
kv => kv.Key,
kv => kv.Value.ToString()
)
);
var serviceResponse = await HttpClient.SendAsync(serviceRequest);
Response.ContentType = "application/xml";
var headersDenyList = new HashSet<string>()
{
"Content-Length",
"Date",
"Transfer-Encoding"
};
foreach (var header in serviceResponse.Headers)
{
if(headersDenyList.Contains(header.Key)) continue;
logger.LogInformation("Header: {Header}, Value: {Value}", header.Key, string.Join(',', header.Value));
Response.Headers.Add(header.Key, new StringValues(header.Value.ToArray()));
}
await serviceResponse.Content.CopyToAsync(Response.Body);
}
}
This will accept the Twilio webhook request, and forward all headers and content to the private web service. Be warned, even though I was able to hack this together until it works, it is probably not secure and not performant. You'll probably have to do a lot more to get this to become production level code. Use at your own risk.
In the ASP.NET Core project for your private service, use a TwilioController to accept the request:
using Microsoft.AspNetCore.Mvc;
using Twilio.AspNet.Common;
using Twilio.AspNet.Core;
using Twilio.TwiML;
namespace Service.Controllers;
public class SmsController : TwilioController
{
private readonly ILogger<SmsController> logger;
public SmsController(ILogger<SmsController> logger)
{
this.logger = logger;
}
public IActionResult Index(SmsRequest smsRequest)
{
logger.LogInformation("SMS Received: {SmsId}", smsRequest.SmsSid);
var response = new MessagingResponse();
response.Message($"You sent: {smsRequest.Body}");
return TwiML(response);
}
}
Instead of proxying the request using the brittle code in the reverse proxy controller, I'd recommend installing YARP in your reverse proxy project, which is an ASP.NET Core based reverse proxy library.
dotnet add package Yarp.ReverseProxy
Then add the following configuration to appsettings.json:
{
...
"ReverseProxy": {
"Routes": {
"SmsRoute" : {
"ClusterId": "SmsCluster",
"Match": {
"Path": "/sms"
}
}
},
"Clusters": {
"SmsCluster": {
"Destinations": {
"SmsService1": {
"Address": "https://localhost:7196"
}
}
}
}
}
}
This configuration will forward any request to the path /Sms, to your private ASP.NET Core service, which on my local machine is running at https://localhost:7196.
You also need to update your Program.cs file to start using YARP:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
When you run both projects now, the Twilio webhook request to /sms is forwarded to your private service, your private service will respond, and your reverse proxy service will forward the response back to Twilio.
Using YARP you can do a lot more through configuration or even programmatically, so if you're interested I'd check out the YARP docs.
If you already have a reverse proxy like NGINX, HAProxy, F5, etc. it may be easier to configure that to forward your request instead of using YARP.
PS: Here's the source code for the hacky and YARP solution

How to pass authorization token in every request

After the user logs in I verify their info and generate a JWT token.
Authentication process happens with Authentication (it's not my custom handler).
Where and how do I save this token so it will be sent along the http calls? I don't want to save it in the client side because of XSS attacks. The following doesn't seem to work either as I wont be in every request
HttpContext.Request.Headers.Append("Authorization", MyGeneratedJWTTokenAsString);
I have found answers that use HttpClient.Request but is there any other secure way of doing this?
When using HttpClient in a backend service, it is always good to use the IHttpClientFactory to generate clients.
So, what we are going to do is use this factory (in conjunction with IHttpContextAccessor) to produce HttpClient objects that have the current user's authorization scheme and token. So, add this to your ConfigureServices method in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddHttpClient("UserAuthorizedHttpClient", (sp, httpClient) =>
{
var accessor = sp.GetRequiredService<IHttpContextAccessor>();
if (accessor.HttpContext.Request.Headers.TryGetValue(
"Authorization", out var authHeaderValue) &&
AuthenticationHeaderValue.TryParse(
authHeaderValue, out var auth))
{
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(auth.Scheme, auth.Parameter);
}
else
{
// incase there is a value from a previous generation
if(httpClient.DefaultRequestHeaders.Contains("Authorization"))
{
httpClient.DefaultRequestHeaders.Remove("Authorization");
}
}
});
services.AddHttpContextAccessor();
// ...
}
In order to use these special clients, you simply inject IHttpClientFactory in to the service that needs to make the HTTP requests:
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace YouApplicationNamespace.Services
{
public interface IMyHttpRequesterService
{
Task DoSomethingCoolAsync();
}
public sealed class MyHttpRequesterService : IMyHttpRequesterService
{
private readonly IHttpClientFactory _httpClientFactory;
public MyHttpRequesterService(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public async Task DoSomethingCoolAsync()
{
var authroizedHttpClient =
_httpClientFactory.CreateClient("UserAuthorizedHttpClient");
var resp = await authroizedHttpClient.GetAsync(new Uri("https://www.example.com/"));
// ...
}
}
}
As long as you use the same name, you will get a client that uses the AddHttpClient routine in your configuration.
(Please note: this code is not tested. It is more of a guideline)

In ASP.NET Core, can the application generate a new request to itself without doing IO?

I'm implementing a custom ASP.NET Core middleware to handle the ETag/If-Match pattern to prevent lost updates. My HTTP GET operations will return an ETag value in the header and every PUT operation will be required to include that ETag in the If-Match header. I'm hashing the body of the GET responses to generate the ETag value. I've currently implemented the middleware using the HttpClient to perform the GET operation when I need to check the If-Match header. This works but requires a network/out-of-process call. Shouldn't there be a better performing way to call the ASP.NET Core HTTP pipeline without leaving the process? Can the application generate a new request to itself without doing IO? Here's the code currently for my middleware:
public class ETagIfMatchMiddleware : IMiddleware
{
//client for my asp.net core application
public static HttpClient client = new HttpClient { BaseAddress = new Uri("https://localhost:5001") };
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var request = context.Request;
if (request.Method == HttpMethods.Put)
{
var ifMatch = request.Headers[HeaderNames.IfMatch];
//requires network out of process call
var response = await client.GetAsync(request.GetEncodedUrl());
string eTag = response.Headers.ETag.Tag;
if (eTag != ifMatch)
{
context.Response.StatusCode = StatusCodes.Status412PreconditionFailed;
return;
}
}
await next(context);
}
}

How to include credentials when making CORS requests from Blazor?

On a blazor client application, what is the equivalent of jQuery ajax WithCredentials or JavaScript credentials: 'include'?
With Javascript I am able to say:
fetch('https://www.example.com/api/test', {
credentials: 'include'
});
which includes auth cookie while making request and server responds with 200. I am trying to write same with Blazor using HttpClient.
You can no longer set WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include; in your Startup file to achieve js`s credentials: 'include'
To achieve this in newer versions of blazor, you need to create a class that derives from DelegatingHandler, override SendAsync method and set BrowserRequestCredentials for request to BrowserRequestCredentials.Include
public class CookieHandler : DelegatingHandler
{
public CookieHandler()
{
InnerHandler = new HttpClientHandler();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return await base.SendAsync(request, cancellationToken);
}
}
After it pass your CookieHandler to HttpClient
builder.Services.AddScoped(sp => new HttpClient(new CookieHandler()) { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
In your Startup.Configure method you can set the WebAssemblyHttpMessageHandler.DefaultCredentials to the required value of the 'credentials' option on outbound HTTP requests like this:
public void Configure(IComponentsApplicationBuilder app)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY")))
{
WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
}
app.AddComponent<App>("app");
}
References:
https://github.com/aspnet/AspNetCore/blob/c39fbb8f12002f61df6c093b0c11e6bd585ee202/src/Components/Blazor/Blazor/src/Http/WebAssemblyHttpMessageHandler.cs
https://github.com/aspnet/AspNetCore/blob/5a70f5312fb57fc3788e5af56a99e7b43761e195/src/Components/Blazor/Blazor/src/Http/FetchCredentialsOption.cs
https://github.com/aspnet/AspNetCore/blob/d18a033b1ee6d923a72d440718c5d496b57c2ffc/src/Components/test/testassets/BasicTestApp/Startup.cs
Hope this helps...

Does .NET Core HttpClient have the concept of interceptors?

I would like to wrap some timing logic around all calls made through HttpClient from my ASP.NET Core app, including calls made from 3rd party libraries.
Does HttpClient in .NET Core have something I can plug into to run some code on every request?
Yes, it does. HttpClient produces a HTTP request via DelegatingHandler chain. To intercept the HttpClient request, you can add a derived handler with overrided SendAsync method to that chain.
Usage:
var handler = new ExampleHttpHandler(fooService);
var client = new HttpClient(new ExampleHttpHandler(handler));
var response = await client.GetAsync("http://google.com");
Implementation:
public class ExampleHttpHandler : DelegatingHandler
{
//use this constructor if a handler is registered in DI to inject dependencies
public ExampleHttpHandler(FooService service) : this(service, null)
{
}
//Use this constructor if a handler is created manually.
//Otherwise, use DelegatingHandler.InnerHandler public property to set the next handler.
public ExampleHttpHandler(FooService service, HttpMessageHandler innerHandler)
{
//the last (inner) handler in the pipeline should be a "real" handler.
//To make a HTTP request, create a HttpClientHandler instance.
InnerHandler = innerHandler ?? new HttpClientHandler();
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
//add any logic here
return await base.SendAsync(request, cancellationToken);
}
}
BTW, I recommend moving as much business logic out of a custom handler as possible to simplify unit-testing it.