Using VS 2017 .Net 4.5.2
I have the following class
public static class MyHttpClient
{
//fields
private static Lazy
update: added the SemaphoreSlim to "lock" the refresh transaction
disclaimer: not tested, might need some tweaking
note 1: the semaphore needs to be in a try/catch/finaly block to ensure release if an error is thrown.
note 2: this version will queue the refresh token calls which significant degrades performance if load is high. To fix this; use a bool indicator to check if the refresh is occurred. This might be a static bool for example
The goal is to only use the refresh token if needed. A fixed interval won't help you because, one day, this interval might change. The correct way to handle this is to retry if a 403 occurs.
You can use a HttpClientHandler to work with your HttpClient.
Override the SendAsync, to handle and retry the 403.
For this to work you'll need this constructor of httpclient:
From the top of my (semi) head, it must be something like this:
the HttpMessageHandler
public class MyHttpMessageHandler : HttpMessageHandler
{
private static SemaphoreSlim sem = new SemaphoreSlim(1);
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
//test for 403 and actual bearer token in initial request
if (response.StatusCode == HttpStatusCode.Unauthorized &&
request.Headers.Where(c => c.Key == "Authorization")
.Select(c => c.Value)
.Any(c => c.Any(p => p.StartsWith("Bearer"))))
{
//going to request refresh token: enter or start wait
await sem.WaitAsync();
//some typical stuff
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("refresh_token", yourRefreshToken),
new KeyValuePair<string, string>("client_id", yourApplicationId),
};
//retry do to token request
using ( var refreshResponse = await base.SendAsync(
new HttpRequestMessage(HttpMethod.Post,
new Uri(new Uri(Host), "Token"))
{
Content = new FormUrlEncodedContent(pairs)
}, cancellationToken))
{
var rawResponse = await refreshResponse.Content.ReadAsStringAsync();
var x = JsonConvert.DeserializeObject<RefreshToken>(rawResponse);
//new tokens here!
//x.access_token;
//x.refresh_token;
//to be sure
request.Headers.Remove("Authorization");
request.Headers.Add("Authorization", "Bearer " + x.access_token);
//headers are set, so release:
sem.Release();
//retry actual request with new tokens
response = await base.SendAsync(request, cancellationToken);
}
}
return response;
}
}
}
send example, with SendAsync (could also be GetAsync) etc.
public async Task<int> RegisterAsync(Model model)
{
var response = await YourHttpClient
.SendAsync(new HttpRequestMessage(HttpMethod.Post, new Uri(BaseUri, "api/Foo/Faa"))
{
Content = new StringContent(
JsonConvert.SerializeObject(model),
Encoding.UTF8, "application/json")
});
var result = await response.Content.ReadAsStringAsync();
return 0;
}