How to handle token expiration in httpclient? - c#

I am in front of problem below.
I am using a httpclient for make requests. Also we use bearer authorization and getting token from server each one hour.
I am trying to find which is the best solution for checking if our token has been expired.
Here is an example from server Response when we generate a token
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjBEOEZCOEQ2RURFQ0Y1Qzk3RUY1MjdDMDYxNkJCMjMzM0FCNjVGOUZSUzI1NiIsInR5cCI6ImF0K2p3dCIsIng1dC",
"expires_in": 3600, //seconds
"token_type": "Bearer",
}
First solution is to create a static datetime. Then save token expiration datetime in this static datetime.
Then in each request to compare current datetime with expiration datetime, and if need to generate a new token.
Is there any way to register a method in httpclient? So in each request to run this method first?
We tried to create a static method, and place it before each httpclient request.
But we don't want to copy paste this method before each request. If there is a method to register a method in httpclient
Here is an httpclient example
public class HttpClientHelper
{
private static HttpClient httpClient;
public static HttpClient Request()
{
if (httpClient == null)
httpClient = new HttpClient();
return httpClient;
}
}
And here is how you are making a request
var uri = "http://myurl.com";
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri))
{
using (var httpResponseMessage = await HttpClientHelper.Request().SendAsync(httpRequestMessage,
new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token))
{
if (httpResponseMessage.IsSuccessStatusCode)
{
}
}

You should be able to store the token in local memory cache and set an expiration. When the JWT is about to expire then it should invalidate the cache entry.
IMemoryCache cache = provider.GetRequiredService<IMemoryCache>();
var token = cache.Get("access_token");
if (!string.IsNullOrWhitespace(token))
{
return token;
}
var newToken = GetAccessToken();
cache.Set("access_token", newToken, TimeSpan.FromMinutes(55)); // assuming tokens expire in 1hr

Related

Trouble posting more than once to webapi from Blazor component- "This instance has already started one or more requests." [duplicate]

I am creating an application in .Net Core 2.1 and I am using http client for web requests. The issue is I have to send parallel calls to save time and for that I am using Task.WhenAll() method but when I hit this method I get the error "This instance has already started one or more requests. Properties can only be modified before sending the first request" Previously I was using RestSharp and everything was fine but I want to use httpclient. Here is the code:
public async Task<User> AddUser(string email)
{
var url = "user/";
_client.BaseAddress = new Uri("https://myWeb.com/");
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(Constants."application/json"));
_client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var json = new {email = email }
var response = await _client.PostAsJsonAsync(url,json);
if (response .IsSuccessStatusCode)
{ ....
Here is the constructor:
private readonly HttpClient _httpClient;
public UserRepository(HttpClient httpClient)
{
_httpClient = httpClient;
}
Method calling:
var user1 = AddUser("user#user.com");
var user2 = AddUser("test#test.com");
await Task.WhenAll(user1, user2);
and here is the startup configuation:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
So what am I doing wrong? Do I need to change AddSingleton with AddTransient() or is there any other issue. One more question do I need to use _client.Dispose() after the response because the tutorial which I followed didn't use dispose method so I am little confused in that.
HttpClient.DefaultRequestHeaders (and BaseAddress) should only be set once, before you make any requests. HttpClient is only safe to use as a singleton if you don't modify it once it's in use.
Rather than setting DefaultRequestHeaders, set the headers on each HttpRequestMessage you are sending.
var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent("{...}", Encoding.UTF8, "application/json");
var response = await _client.SendAsync(request, CancellationToken.None);
Replace "{...}" with your JSON.
Maybe my two cents will help someone.
I ran into this issue when refreshing the page when debugging the application.
I was using a singleton, but each refresh, it was trying to set the base address. So I just wrapped it in a check to see if the base address had already been set.
The issue for me was, it was trying to set the baseAddress, even though it was already set. You can't do this with a httpClient.
if (_httpClient.BaseAddress == null)
{
_httpClient.BaseAddress = new Uri(baseAddress);
}
The issue is caused by resetting BaseAddress and headers for the same instance of the httpclient.
I tried
if (_httpClient.BaseAddress == null)
but I am not keen on this.
In my opinion, a better soloution is to use the httpclientFactory. This will terminate and garbage collect the instance of the httpclient after its use.
private readonly IHttpClientFactory _httpClientFactory;
public Foo (IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public httpresponse Bar ()
{
_httpClient = _httpClientFactory.CreateClient(command.ClientId);
using var response = await _httpclient.PostAsync(uri,content);
return response;
// here as there is no more reference to the _httpclient, the garbage collector will clean
// up the _httpclient and release that instance. Next time the method is called a new
// instance of the _httpclient is created
}
It Works well when you add the request url and the headers at the message, rather than at the client. So better not to assign to BaseAddress Or the header DefaultRequestHeaders if you will use them for many requests.
HttpRequestMessage msg = new HttpRequestMessage {
Method = HttpMethod.Put,
RequestUri = new Uri(url),
Headers = httpRequestHeaders;
};
httpClient.SendAsync(msg);

Authorization header is not setting

I have one method for login as per below :
public async Task<ActionResult> Index(LoginModel loginModel)
{
string url = "API_URL";
var response = await client.PostAsJsonAsync(url, loginModel);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
var jsonData = JObject.Parse(result);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", "Bearer " + jsonData["accessToken"]);
return RedirectToAction("PostLogin");
}
else
{
ModelState.AddModelError("", "These credentials does not work. Hmm..");
}
return View(loginModel);
}
Now in this if user successfully login then I am getting token and also I am setting that in header. Now after that I am redirecting user to PostLogin method.
But in that method I am not able to access that token. Below is code for that.
public async Task<ActionResult> PostLogin()
{
var accessToken = await HttpContext.GetTokenAsync("accessToken"); // Here I can't see token. and getting value as per below.
return View();
}
Even I am getting this error as per below :
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
You have commented that at start of controller you have HttpClient client = new HttpClient(); which is new instance of HttpClient
So in your function you are setting that objects authorization using this line: client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", "Bearer " + jsonData["accessToken"]);.
After that you are getting users authorization from current HttpContext which is sent each time user visits some link and it does not have anything with your client object you just set authorization to.
Since you are redirecting you must change current HttpContext authorization header since it will be carried to redirected request. Do it by changing HttpContext.Request.Headers["Authorization"]. Problem is that it will only last that request so you need to give token back to client so he sends it in header each time he makes request.

Simple way to transfer cookies from current HttpContext into new created HttpClient request

I'm trying to wrap some api request
[Route("foo")]
public Task Foo()
{
using var http = new HttpClient();
return http.PostAsync(
Endpoint,
new FormUrlEncodedContent(new Dictionary<string, string>
{
{ ClientId, "ClientId" },
}),
CancellationToken.None)
.ConfigureAwait(false);
}
and getting issue with it. Cause is in cookies that uses by called endpoint.
Is there any way to transfer cookies from my current HttpContex into HttpClient Post call? I know, I can use CookieContainer, HttpClientHandler and pass all this stuff into HttpClient, but I would like use something more elegance.
Just grab the Cookie header from the incoming request and add to the outgoing one. Setting a header on an individual request will require creating an HttpRequestMessage explicitly and using HttpClient.SendAsync to send it, but that's fairly simple:
var outgoing = new HttpRequestMessage(HttpMethod.Post, uri);
outgoing.Content = new FormUrlEncodedContent(...);
if (Request.Headers.TryGetValue("Cookie", out var cookies))
{
outgoing.Headers.TryAddWithoutValidation("Cookie", cookies);
}
await http.SendAsync(outgoing);

Single Instance of HttpClient vs Multiples Instances with ClientCredentias?

I'm using HttpClient to make request to a Soap Api.
I saw that is recommended to use a single instance of HttpClient instead once for each request to avoid SocketException.
In my Desktop app, I need to make multiples requests, each one with differents credentials. I'm doing it like the sample code:
WebRequestHandler requestHandler = new WebRequestHandler();
requestHandler.ClientCertificates.Add(certificate);
using(var client = new HttpClient(requestHandler))
{
try
{
HttpResponseMessage response = await client.PostAsync("https://nfe-homologacao.svrs.rs.gov.br/ws/nfeinutilizacao/nfeinutilizacao4.asmx", httpContent);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
return responseBody;
}
catch (HttpRequestException e)
{
return e.Message;
}
}
However, I'd like to avoid creates an instance of HttpClient for each request. I saw that correct way is use static instance like this:
static readonly HttpClient client = new HttpClient();
But with static instance, I can't set credentials with WebRequestHandler. Please note that credentials change in each request.
What is the better way to perform all requests, avoiding SocketException and using differents credentials?
Generally per request credentials should be set for each request created:
var request = new HttpRequestMessage();
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer");
await client.SendAsync(request);
You can also do this in your own web request handler:
class SetCredentialsWebRequestHandler : WebRequestHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer");
return base.SendAsync(request, cancellationToken);
}
}
client = new HttpClient(new SetCredentialsWebRequestHandler());
await client.PostAsync(url);
UPDATE
From the comments, I learned the question requires using certificate authentication. So in this case, multiple instances (one per certificate) would be a better option.
There will be not point to reuse the same connection for different client certificate.
Check out this named HttpClient:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1#named-clients
HttpClient could be named as per the certificate.

c# Httpclient singleton with same authentication header on multiple client/user

I have an azure function that calls API and I made my HttpClient as a singleton in the startup Dependency Injection so I can call it on my contractor. My code below calls 2 API with the same Authentication header.
public class MyClass : IMyClass
{
private readonly HttpClient _httpClient;
public MyClass(HttpClient httpClient)
{
_httpClient = httpClient;
}
public void test(string OAuthToken)
{
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", OAuthToken);
// 1st API
string firstApi = $"https://GetSometthingFirst.com/processes?api-version=5.0";
var GetFirst = _httpClient.GetAsync(firstApi).Result;
// add delay 5 secs
Thread.Sleep(5000);
// 2nd API
string secondApi = $"https://GetSometthingSecond.com/processes?api-version=5.0";
var content = new StringContent(GetFirst.ToString(), Encoding.UTF8, "application/json");
var result = _httpClient.PostAsync(secondApi, content).Result;
}
}
In the code above I have two API calls which use the same DefaultRequestHeader. Since it's a singleton the function can call by multiple user with different OAthToken as the parameter and share the same instance of HttpClient. Should I need to refresh the Default header like this so the other thread wont be affected?
public void test(string OAuthToken)
{
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", OAuthToken);
// 1st API
string firstApi = $"https://GetSometthingFirst.com/processes?api-version=5.0";
var GetFirst = _httpClient.GetAsync(firstApi).Result;
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", OAuthToken);
// add delay 5 secs
Thread.Sleep(5000)
// 2nd API
string secondApi = $"https://GetSometthingSecond.com/processes?api-version=5.0";
var content = new StringContent(GetFirst.ToString(), Encoding.UTF8, "application/json");
var result = _httpClient.PostAsync(secondApi, content).Result;
}
The official documentation suggests you use HttpClientFactory to implement resilient HTTP requests . It makes the management of HttpClient instances easier.

Categories