How to pass authorization token in every request - asp.net-core

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)

Related

Setup JWT from headers to be used by default in HttpClient

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 =)

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 implement custom ValidateAntiforgeryTokenAuthorizationFilter in ASP.NET Core 3.1

I'd like to implement a filter that skips validation of an antiforgery token when an auth token authentication (Bearer) is used.
In the ASP.NET Core 2.2 the ValidateAntiforgeryTokenAuthorizationFilter and AutoValidateAntiforgeryTokenAuthorizationFilter were public (even though living in the Microsoft.AspNetCore.Mvc.ViewFeatures.Internal namespace), so I was able to just inherit from the latter and override the ShouldValidate method easily.
In the ASP.NET Core 3.0 they became internal, so it's not possible to just inherit from them. I can just copy-paste the code, but it's not the ideal solution obviously.
I was following the Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core article from MSDN, but it doesn't really mention anything relevant to my scenario.
Normally you can use [IgnoreAntiforgeryToken] attribute if you can determine at compile-time that the csrf token should be ignored. If you want such an ability at run-time, you could create a custom FilterProvider that will provide an IAntiforgeryPolicy if there's a Authroization: Bearer json-web-token header.
For example, we can create a custom AutoSkipAntiforgeryFilterProvider as below:
public class AutoSkipAntiforgeryFilterProvider: IFilterProvider
{
private const string BEARER_STRING = "Bearer";
public int Order => 999;
public void OnProvidersExecuted(FilterProviderContext context) { }
public void OnProvidersExecuting(FilterProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
if (context.ActionContext.ActionDescriptor.FilterDescriptors != null)
{
var headers = context.ActionContext.HttpContext.Request.Headers;
if (headers.ContainsKey("Authorization"))
{
var header = headers["Authorization"].FirstOrDefault();
if(header.StartsWith(BEARER_STRING,StringComparison.OrdinalIgnoreCase))
{
var FilterDescriptor = new FilterDescriptor(SkipAntiforgeryPolicy.Instance, FilterScope.Last);
var filterItem = new FilterItem( FilterDescriptor,SkipAntiforgeryPolicy.Instance);
context.Results.Add(filterItem);
}
}
}
}
// a dummy IAntiforgeryPolicy
class SkipAntiforgeryPolicy : IAntiforgeryPolicy, IAsyncAuthorizationFilter
{
// a singleton
public static SkipAntiforgeryPolicy Instance = new SkipAntiforgeryPolicy();
public Task OnAuthorizationAsync(AuthorizationFilterContext context) => Task.CompletedTask;
}
}
And register this filter provider in Startup :
services.TryAddEnumerable( ServiceDescriptor.Singleton<IFilterProvider, AutoSkipAntiforgeryFilterProvider>());
Now it will bypass the AntiForgery even there's a [ValidateAntiForgeryToken]attribute.
[Demo]
Assume we have an action method annotated with [ValidateAntiForgeryToken]:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name")] XModel xModel)
{
....
}
Normally, it will protect this method with CSRF token. But if you send a request like:
POST /XModels/Create HTTP/1.1
Authorization: Bearer Xyz
Content-Type: application/x-www-form-urlencoded
...
it won't validate the csrf token.

Register scoped based HttpClient in .NET Core 2

I have NET Core 2 Web API application. During the process i have to invoke Client A's API to get some data. So i am using HttpClient to invoke it. Client A also requires me to pass userid and password in header.
So instead of directly injecting HttpClient i have wrapper around HttpClient something like below
public class ClientA : IClientA
{
private readonly HttpClient _httpClient;
public ClientA(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetData()
{
return await _httpClient.HttpGetAsync("someurl");
}
}
Then use ClientA in Service
public class MyService :IMyService
{
private readonly IClientA _clientA
public MyService(IClientA clientA)
{
_clientA= clientA
}
public void DoSomethig()
{
_clientA.GetData();
}
}
Then i am registering everything in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyService, MyService>();
services.AddScoped(factory =>
{
Func<Task<IClientA>> provider = async () =>
{
using (var dbContext = factory.GetService<MyDBContext>())
{
// get userid and password from database here
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("UserId",userid);
httpClient.DefaultRequestHeaders.Add("Password",password);
return new ClientA(httpClient);
}
};
return provider;
});
}
However i am getting error
System.InvalidOperationException: Unable to resolve service for type
'System.Net.Http.HttpClient' while attempting to activate
'XXXXXXXXX.ClientA'. at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type
serviceType, Type implementationType, ISet1 callSiteChain,
ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type
serviceType, Type implementationType, ISet1 callSiteChain)
remaining exception removed for brevity
Notice that during registration i am newing-up instance of HttpClient and passing it to ClientA class because i have to set userid and password.
To get rid the above error I can register HttpClient with UserID and Password with DI framework and i guess that would work.
However, in that case, if have another client, ClientB, that takes HttpClient then DI framework will inject same httpclient that has userid and password. and that will create security issue because ClientB would see ClientA's credentials in request headers.
public class ClientB(HttpClient client)
{
private readonly _httpClient;
public class ClientB(HttpClient client)
{
_httpClient = client;
}
public string CallClientB(string url)
{
// here ClientB will receive ClientA's credentials
return await _httpClient.HttpGetAsync(url);
}
}
You don't want to be instantiating httpclient in a scoped context, that is creating an instance of httpclient per request which is not the recommended usage pattern for that class. (won't scale well). https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
Create a singleton with a separate interface per customer (assuming a small number of customers) - possibly with a code access security demand in its implementation, depending on your setup (identity impersonation enabled?)
That will a) scale well b) only run once per customer per application instance/startup and c) enforce an access check for usage.
Also, this answer is connected and relevant to your header requirements - HttpClient single instance with different authentication headers
resolved my issue
services.AddScoped<IClientA>(factory =>
{
var dbContext = factory.GetService<MyDBContext>();
// get userid and password from database here
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("UserId",userid);
httpClient.DefaultRequestHeaders.Add("Password",password);
return new ClientA(httpClient);
});

How are bearer tokens stored server-side in Web API 2?

I am setting up bearer token authentication in Web API 2, and I don't understand how (or where) the bearer token is being stored server-side. Here is the relevant code:
Startup:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<UserManager<IdentityUser>> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOAuthBearerTokens(OAuthOptions);
}
}
WebApiConfig:
public class WebApiConfig
{
public static void ConfigureWebApi()
{
Register(GlobalConfiguration.Configuration);
}
public static void Register(HttpConfiguration http)
{
AuthUtil.ConfigureWebApiToUseOnlyBearerTokenAuthentication(http);
http.Routes.MapHttpRoute("ActionApi", "api/{controller}/{action}", new {action = Actions.Default});
}
}
AuthUtil:
public class AuthUtil
{
public static string Token(string email)
{
var identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, email));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
var token = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
return token;
}
public static void ConfigureWebApiToUseOnlyBearerTokenAuthentication(HttpConfiguration http)
{
http.SuppressDefaultHostAuthentication();
http.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
}
}
LoginController:
public class LoginController : ApiController
{
...
public HttpResponseMessage Post([FromBody] LoginJson loginJson)
{
HttpResponseMessage loginResponse;
if (/* is valid login */)
{
var accessToken = AuthUtil.Token(loginJson.email);
loginResponse = /* HTTP response including accessToken */;
}
else
{
loginResponse = /* HTTP response with error */;
}
return loginResponse;
}
}
Using the above code, I'm able to login and store the bearer token client-side in a cookie, and then make calls to controllers marked with [Authorize] and it lets me in.
My questions are:
Where / how is the bearer token being stored server-side? It seems like this is hapenning through one of the OWIN calls but I can't tell where.
Is it possible to persist the bearer tokens to a database server-side so that they can remain in place after a Web API server restart?
If the answer to #2 is no, is there anyway for a client to maintain its bearer token and re-use it even after the Web API goes down and comes back up? While this may be rare in Production, it can happen quite often doing local testing.
They're not stored server side -- they're issued to the client and the client presents them on each call. They're verified because they're signed by the owin host's protection key. In SystemWeb hosting, that protection key is the machineKey setting from web.config.
That's unnecessary, as long as the protection key the owin host uses doesn't change across server restarts.
A client can hold onto a token for as long as the token is valid.
For those who are looking for how to set web.config, here is a sample
<system.web>
<machineKey validation="HMACSHA256" validationKey="64-hex"
decryption="AES" decryptionKey="another-64-hex"/>
</system.web>
You need both validationKey and decriptionkey to make it work.
And here is how to generate keys
https://msdn.microsoft.com/en-us/library/ms998288.aspx
To add to this, the token can be persisted server side using the SessionStore property of of CookieAuthenticationOptions. I wouldn't advocate doing this but it's there if your tokens become excessively large.
This is an IAuthenticationSessionStore so you could implement your own storage medium.
By default the token is not stored by the server. Only your client has it and is sending it through the authorization header to the server.
If you used the default template provided by Visual Studio, in the Startup ConfigureAuth method the following IAppBuilder extension is called: app.UseOAuthBearerTokens(OAuthOptions).