I am using ADAL tokens to call my WebAPI's in a Xamarin Forms project. But the ADAL tokens expires after 1 hour and the http calls throws Unauthorized.
I want to refresh the tokens when Unauthorized status is recieved. I read about and some examples say to use DelegatingHandler , but I don't know what DelegatingHandler is and how to use it with the HTTP client.
According to your description, I checked this issue. For using DelegatingHandler, you could refer to the following code snippet:
CustomMessageHandler:
public class CustomMessageHandler: DelegatingHandler
{
private string _accessToken;
private string _refreshToken;
public CustomMessageHandler(string accessToken, string refreshToken)
{
_accessToken = accessToken;
_refreshToken = refreshToken;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
var response= await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
/* TODO:
* 1. Retrieve the new access_token via the refresh_token
* 2. Update the current field _accessToken
* 3. Retry the previous failed request
*/
}
return response;
}
}
Usage:
HttpClient client = new HttpClient(new CustomMessageHandler("{your-access-token}", "{your-refresh-token}"));
Related
I make webservice api based on this tutorial https://www.c-sharpcorner.com/article/asp-net-mvc-oauth-2-0-rest-web-api-authorization-using-database-first-approach/ and I need to consume the service form xamarin forms. But I don't know how to authorize client.
Before you try authorizing in code, you should try talking to your API via an api client such as Postman.
You can see in step 11 of the article you reference that the writer is infact doing this.
He is performing the following steps:
Calling the token endpoint (no auth)
Adding the token to his subsequent requests
In order to call an API with authorization, you must first know the auth method (basic, OAuth etc). In this case you're saying it's OAuth:
Take a look at the guide, it shares this picture:
To do this in code you will need to add the following header to your http client. Lets assume you're using vanilla System.Net.Http.HttpClient you would need to implement a class that looks something like this:
public class APIClient
{
private HttpClient _client;
public APIClient()
{
_client = SetupClient();
}
private HttpClient SetupClient()
{
//setup your client here
var client = new HttpClient();
//string oauthToken = TokenService.GetUserToken();
string oauthToken = "eyJhbGciO......."; //Example token
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {oauthToken}");
//more setup here
return client;
}
public async Task<HttpResponseMessage> Get(string endpoint)
{
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
return await CallAsync(request);
}
private async Task<HttpResponseMessage> CallAsync(HttpRequestMessage request)
{
//do things before?
var result = await _client.SendAsync(request);
//handle result? null? not success code?
return result;
}
}
When you initialise your HttpClient you should add the following header:
Authorization : Bearer {yourtoken}
Now subsequent api requests will have authorization from your api client. How you set this bearer value is up to you. Some people store credentials in the xamarin main App class, and then retrieve the property. Other will persist the data into a plist and have the apiclient read this value (maybe credentials expire every 30 days).
Regardless there are a number of pitfalls that come with talking to api's from a xamarin app. You should always start by calling your api from outside of your app, from within an api client. This will teach you how to configure the requests correctly, without the overhead of worrying if your code/configuration is correct.
Please check my class if help you
`public class ServicesClient
{
private HttpClient httpClient;
private bool _IsConnection { get { return CheckInternet(); } }
public bool IsConnection { get { return _IsConnection; } }
public ServicesClient()
{
httpClient = new HttpClient(new HttpClientHandler());
//You can change the key as you need and add value
httpClient.DefaultRequestHeaders.Add("key", "000000");
}
//Get Method
public async Task<T> GetAsync<T>(string URL) where T : class
{
if (IsConnection)
{
var result = await httpClient.GetStringAsync(URL);
if (!string.IsNullOrEmpty(result))
return JsonConvert.DeserializeObject<T>(result);
else
return null;
}
return null;
}
//Post Method
public async Task<T> PostAsync<T>(string URL, object param) where T : class
{
if (IsConnection)
{
var json = JsonConvert.SerializeObject(param);
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
var result = await httpClient.PostAsync(URL, httpContent);
if (result.IsSuccessStatusCode)
return JsonConvert.DeserializeObject<T>(result.Content.ReadAsStringAsync().Result);
}
return null;
}
bool CheckInternet()
{
return Connectivity.NetworkAccess == NetworkAccess.Internet;
}
}
}`
I am using IHttpClientFactory for sending requests and receiving HTTP responses from an external APIs using Net Core 2.2.
I have implemented a DelegatingHandler to "intercept" my http request and add the Authorization header (Token). if token is not valid, It gets a new token and retry one more time.
Likewise, when I get a new token for the first time, I cache the token in-memory for further references. For caching the token I have created a dictionary that requires an accountID and the token.
The problem I have got is that the DelegatingHandler is registered in the Startup.cs class, but at that moment I do not have the accountID, I get the accountID as a parameter in the ActionMethod of the Controller. That action method is the one calling SendAsync and getting the token from the DelegatingHandler and so on.
I do not know, how I can inject that accountID into the DelegatingHandler after a request has been received in the controller.
I was trying creating a IClientCredentials interface and an implementation of that interface that can be instantiated in the controller and injected into the DelegatingHandler.
My Code looks like this:
The DelegatingHandler:
public class AuthenticationDelegatingHandler : DelegatingHandler
{
private readonly AccessTokenManager _accessTokenManager;
private readonly IClientCredentials _clientCredentials;
public AuthenticationDelegatingHandler(IHttpClientFactory httpClientFactory,
IOptions<AppSettings> appSettings, IClientCredentials clientCredentials)
{
_accessTokenManager = new AccessTokenManager(httpClientFactory, appSettings);
_clientCredentials = clientCredentials;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var clientCredentials = _clientCredentials.GetClientCredentials();
var accessToken = _accessTokenManager.GetToken(clientCredentials._accountID);
if (accessToken == null) {
accessToken = await _accessTokenManager.GetAccessTokenAsync(clientCredentials._accountID);
_accessTokenManager.AddOrUpdateToken(clientCredentials._accountID, accessToken);
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.access_token);
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
{
var token = await _accessTokenManager.GetAccessTokenAsync(clientCredentials._accountID);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.access_token);
response = await base.SendAsync(request, cancellationToken);
}
return response;
}
}
Startup.cs like that:
services.AddScoped<IClientCredentials>(_ => new
ClientCredentials("au","123"));
services.AddHttpClient("myClient")
.AddHttpMessageHandler<AuthenticationDelegatingHandler>();
And The controller:
[HttpPost("{siteName}/{accountID}")]
public async Task<ActionResult<AirRequest>> Post(AirModel model, string
siteName, string accountID)
{
....
SetClientCredentials(siteName, accountID);
var clientJAAPI =
_httpClientFactory.CreateClient("myClient");
var responseclientJAAPI = await
clientJAAPI.SendAsync(request);
.....
}
private ClientCredentials SetClientCredentials(string siteName, string
accountID) =>
new ClientCredentials(siteName, accountID);
You can use HttpContext.Items to pass the data.
(Not tested, sent from mobile).
In controller:
this.HttpContext.Items["accountId"] = accountId;
In your Handler inject IHttpContextAccessor
var accountId = _httpContextAccessor.HttpContext.Items["accountId"];
IHttpContextAccessor not registered by default, but can be registered by one of components you are using. If you get an exception, register it explicetly in DI:
services.AddHttpContextAccessor();
If IHttpContextAccessor type is missing add Microsoft.AspNetCore.Http nuget.
The data will sit there untill end of the request.
Writing code for controllers could lead to repeat myself again and again.
How can reuse the code below and apply DRY principle on C# Net Core 2.0. MVC controllers?
See the below example.
The coding for getting a full list of departments using EF and web API is as follows..
[HttpGet]
public async Task<IActionResult> Department()
{
using (var client = await _apiHttpClient.GetHttpClientAsync())
{
var response = await client.GetAsync("api/Department");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var dptos = JsonConvert.DeserializeObject<Department[]>(content);
return View(dptos);
}
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
return RedirectToAction("AccessDenied", "Authorization");
throw new Exception($"A problem happened while calling the API: {response.ReasonPhrase}");
}
}
Is indeed almost identical to get a single department..
[HttpGet]
public async Task<IActionResult> DeparmentEdit(string id)
{
ViewData["id"] = id;
using (var client = await _apiHttpClient.GetHttpClientAsync())
{
var response = await client.GetAsync($"api/Department/{id}");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var dpto = JsonConvert.DeserializeObject<Department>(content);
return View(dpto);
}
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
return RedirectToAction("AccessDenied", "Authorization");
throw new Exception($"A problem happened while calling the API: {response.ReasonPhrase}");
}
}
The _apiHttpClient field holds a custom implementation of an HttpClient for tokens and refreshing tokens to access the web API.
I think that IS NOT relevant here to apply refactoring and DRY but anyway I will copy his implementation here below.
BR and thanks in advance for your reply.
public class ApiHttpClient : IApiHttpClient
{
private HttpClient _httpClient;
private HttpClient HttpClient => _httpClient ?? (_httpClient = new HttpClient());
private readonly IHttpContextAccessor _httpContextAccessor;
public ApiHttpClient(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<HttpClient> GetHttpClientAsync()
{
string accessToken;
var context = _httpContextAccessor.HttpContext;
var expiresAt = await context.GetTokenAsync(Constants.Tokens.ExpiresAt); // Get expires_at value
if (string.IsNullOrWhiteSpace(expiresAt) // Should we renew access & refresh tokens?
|| (DateTime.Parse(expiresAt).AddSeconds(-60)).ToUniversalTime() < DateTime.UtcNow) // Make sure to use the exact UTC date formats for comparison
{
accessToken = await RefreshTokensAsync(_httpContextAccessor.HttpContext); // Get the current HttpContext to access the tokens
}
else
{
accessToken = await context.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // Get access token
}
HttpClient.BaseAddress = new Uri(Constants.Urls.ApiHost);
if (!string.IsNullOrWhiteSpace(accessToken))
HttpClient.SetBearerToken(accessToken);
return HttpClient;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing)
{
if (_httpClient != null)
{
_httpClient.Dispose();
_httpClient = null;
}
}
}
public static async Task<string> RefreshTokensAsync(HttpContext context)
{
var discoveryResponse = await DiscoveryClient.GetAsync(Constants.Authority); // Retrive metadata information about our IDP
var tokenClient = new TokenClient(discoveryResponse.TokenEndpoint, Constants.ClientMvc.Id, Constants.ClientMvc.Secret); // Get token client using the token end point. We will use this client to request new tokens later on
var refreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // Get the current refresh token
var tokenResponse = await tokenClient.RequestRefreshTokenAsync(refreshToken); // We request a new pair of access and refresh tokens using the current refresh token
if (tokenResponse.IsError)
return null; // Let's the unauthorized page bubbles up
// throw new Exception("Problem encountered while refreshing tokens", tokenResponse.Exception);
var expiresAt = (DateTime.UtcNow
+ TimeSpan.FromSeconds(tokenResponse.ExpiresIn)).ToString("O", CultureInfo.InvariantCulture); // New expires_at token ISO 860
var authenticateResult = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); // HttpContext.Authentication.GetAuthenticateInfoAsync() deprecated
authenticateResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.AccessToken, tokenResponse.AccessToken); // New access_token
authenticateResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.RefreshToken, tokenResponse.RefreshToken); // New refresh_token
authenticateResult.Properties.UpdateTokenValue(Constants.Tokens.ExpiresAt, expiresAt); // New expires_at token ISO 8601 WHY _at TODO
await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, authenticateResult.Principal, authenticateResult.Properties); // Signing in again with the new values, doing such a user relogin, ensuring that we change the cookies on client side. Doig so the user that has logged in has the refreshed tokens
return tokenResponse.AccessToken;
}
public static async Task RevokeTokensAsync(HttpContext context)
{
var discoveryResponse = await DiscoveryClient.GetAsync(Constants.Authority); // Retrive metadata information about our IDP
var revocationClient = new TokenRevocationClient(discoveryResponse.RevocationEndpoint, Constants.ClientMvc.Id, Constants.ClientMvc.Secret); // Get token revocation client using the token revocation endpoint. We will use this client to revoke tokens later on
var accessToken = await context.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // Get the access token token to revoke
if (!string.IsNullOrWhiteSpace(accessToken))
{
var revokeAccessTokenTokenResponse = await revocationClient.RevokeAccessTokenAsync(accessToken);
if (revokeAccessTokenTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the access token.", revokeAccessTokenTokenResponse.Exception);
}
var refreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // Get the refresh token to revoke
if (!string.IsNullOrWhiteSpace(refreshToken))
{
var revokeRefreshTokenResponse = await revocationClient.RevokeRefreshTokenAsync(refreshToken);
if (revokeRefreshTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the refresh token.", revokeRefreshTokenResponse.Exception);
}
}
}
I had refactored the code as follows having in mind the following workflow.
We will need: a) an API service class, b) a HttpContextAccessor and c) a HttpClient.
1) DI principle!. We register them in our dependency injection container at ConfigureServices
services
.AddTransient<IGameApiService, GameApiService>()
.AddSingleton<IHttpContextAccessor, HttpContextAccessor>()
.AddSingleton(c => new HttpClient { BaseAddress = new Uri(Constants.Urls.ApiHost) });
2) The big job!. The new GameApiService will do the "heavy job" of calling our API methods. We will call the API using a "composed" request string. The API service will use our HttpClient, passing our request string and returning the response code and A STRING! (instead of using generics or other object) with the content. (I would need help on moving to generic since I fear that the registration on the dependency container will be "hard" to do with generics).
(the HttpContextAccessor is used for some token methods)
public class GameApiService : IGameApiService
{
private readonly HttpClient _httpClient;
private readonly HttpContext _httpContext;
public GameApiService(HttpClient httpClient, IHttpContextAccessor httpContextAccessor)
{
_httpClient = httpClient;
_httpContext = httpContextAccessor.HttpContext;
_httpClient.AddBearerToken(_httpContext); // Add current access token to the authorization header
}
public async Task<(HttpResponseMessage response, string content)> GetDepartments()
{
return await GetAsync(Constants.EndPoints.GameApi.Department); // "api/Department"
}
public async Task<(HttpResponseMessage response, string content)> GetDepartmenById(string id)
{
return await GetAsync($"{Constants.EndPoints.GameApi.Department}/{id}"); // "api/Department/id"
}
private async Task<(HttpResponseMessage response, string content)> GetAsync(string request)
{
string content = null;
var expiresAt = await _httpContext.GetTokenAsync(Constants.Tokens.ExpiresAt); // Get expires_at value
if (string.IsNullOrWhiteSpace(expiresAt) // Should we renew access & refresh tokens?
|| (DateTime.Parse(expiresAt).AddSeconds(-60)).ToUniversalTime() < DateTime.UtcNow) // Make sure to use the exact UTC date formats for comparison
{
var accessToken = await _httpClient.RefreshTokensAsync(_httpContext); // Try to ge a new access token
if (!string.IsNullOrWhiteSpace(accessToken)) // If succeded set add the new access token to the authorization header
_httpClient.AddBearerToken(_httpContext);
}
var response = await _httpClient.GetAsync(request);
if (response.IsSuccessStatusCode)
{
content = await response.Content.ReadAsStringAsync();
}
else if (response.StatusCode != HttpStatusCode.Unauthorized && response.StatusCode != HttpStatusCode.Forbidden)
{
throw new Exception($"A problem happened while calling the API: {response.ReasonPhrase}");
}
return (response, content);
}
}
public interface IGameApiService
{
Task<(HttpResponseMessage response, string content)> GetDepartments();
Task<(HttpResponseMessage response, string content)> GetDepartmenById(string id);
}
3) Great DRY! Our MVC controller will use this new API service as follows.. (we really don't have very much code there and THIS IS THE GOAL.. ;-) GREAT!!.
We still keep the responsibility of de-serialize the content string on the controller action on which the service API method was invoked. The code for the service API looks like...
[Route("[controller]/[action]")]
public class DepartmentController : Controller
{
private readonly IGameApiService _apiService;
public DepartmentController(IGameApiService apiService)
{
_apiService = apiService;
}
[HttpGet]
public async Task<IActionResult> Department()
{
ViewData["Name"] = User.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Name)?.Value;
var (response, content) = await _apiService.GetDepartments();
if (!response.IsSuccessStatusCode) return Forbid();
return View(JsonConvert.DeserializeObject<Department[]>(content));
}
[HttpGet]
public async Task<IActionResult> DepartmentEdit(string id)
{
ViewData["id"] = id;
var (response, content) = await _apiService.GetDepartmenById(id);
if (!response.IsSuccessStatusCode) return Forbid();
return View(JsonConvert.DeserializeObject<Department>(content));
}
}
4) Last trick!. To redirect to a custom page when we are not authorized or the permission has been denied we have issued if (!response.IsSuccessStatusCode) return Forbid(); yes Forbid(). But we still need to configure the default denied page on the cookie middleware. Thus on ConfigureServices we do it with services.AddAuthentication().AddCookie(AddCookie) methods, configuring the relevant options, mainly the AccessDeniedPath option as follows.
private static void AddCookie(CookieAuthenticationOptions options)
{
options.Cookie.Name = "mgame";
options.AccessDeniedPath = "/Authorization/AccessDenied"; // Redirect to custom access denied page when user get access is denied
options.Cookie.HttpOnly = true; // Prevent cookies from being accessed by malicius javascript code
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // Cookie only will be sent over https
options.ExpireTimeSpan = TimeSpan.FromMinutes(Constants.CookieTokenExpireTimeSpan); // Cookie will expire automaticaly after being created and the client will redirect back to Identity Server
}
5) A word about the HTTP Client!. It will be instantiated using a factory on the dependency injection. A new instance is created per GameApiService instance.
The helper code to set the bearer token on the header and refresh the access token has been moved to a convenient extension method helper class as follows..
public static class HttpClientExtensions
{
public static async void AddBearerToken(this HttpClient client, HttpContext context)
{
var accessToken = await context.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
if (!string.IsNullOrWhiteSpace(accessToken))
client.SetBearerToken(accessToken);
}
public static async Task<string> RefreshTokensAsync(this HttpClient client, HttpContext context)
{
var discoveryResponse = await DiscoveryClient.GetAsync(Constants.Authority); // Retrive metadata information about our IDP
var tokenClient = new TokenClient(discoveryResponse.TokenEndpoint, Constants.ClientMvc.Id, Constants.ClientMvc.Secret); // Get token client using the token end point. We will use this client to request new tokens later on
var refreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // Get the current refresh token
var tokenResponse = await tokenClient.RequestRefreshTokenAsync(refreshToken); // We request a new pair of access and refresh tokens using the current refresh token
if (tokenResponse.IsError) // Let's the unauthorized page bubbles up instead doing throw new Exception("Problem encountered while refreshing tokens", tokenResponse.Exception)
return null;
var expiresAt = (DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn)).ToString("O", CultureInfo.InvariantCulture); // New expires_at token ISO 860
var authenticateResult = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); // HttpContext.Authentication.GetAuthenticateInfoAsync() deprecated
authenticateResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.AccessToken, tokenResponse.AccessToken); // New access_token
authenticateResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.RefreshToken, tokenResponse.RefreshToken); // New refresh_token
authenticateResult.Properties.UpdateTokenValue(Constants.Tokens.ExpiresAt, expiresAt); // New expires_at token ISO 8601
await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, authenticateResult.Principal, authenticateResult.Properties); // Signing in again with the new values, doing such a user relogin, ensuring that we change the cookies on client side. Doig so the user that has logged in has the refreshed tokens
return tokenResponse.AccessToken;
}
public static async Task RevokeTokensAsync(this HttpClient client, HttpContext context)
{
var discoveryResponse = await DiscoveryClient.GetAsync(Constants.Authority); // Retrive metadata information about our IDP
var revocationClient = new TokenRevocationClient(discoveryResponse.RevocationEndpoint, Constants.ClientMvc.Id, Constants.ClientMvc.Secret); // Get token revocation client using the token revocation endpoint. We will use this client to revoke tokens later on
var accessToken = await context.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // Get the access token token to revoke
if (!string.IsNullOrWhiteSpace(accessToken))
{
var revokeAccessTokenTokenResponse = await revocationClient.RevokeAccessTokenAsync(accessToken);
if (revokeAccessTokenTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the access token.", revokeAccessTokenTokenResponse.Exception);
}
var refreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // Get the refresh token to revoke
if (!string.IsNullOrWhiteSpace(refreshToken))
{
var revokeRefreshTokenResponse = await revocationClient.RevokeRefreshTokenAsync(refreshToken);
if (revokeRefreshTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the refresh token.", revokeRefreshTokenResponse.Exception);
}
}
}
Now the code after refactoring it looks more pretty and clean.. ;-)
You could just split it up using generics. I haven't debugged this code (obviously), but I think it gets you where you need to go.
using System.Security.Authentication;
[HttpGet]
public async Task<IActionResult> Department() {
try {
var myObject = await GetSafeData<Department[]>("api/Department");
return view(myObj);
} catch(AuthenticationException ex) {
return RedirectToAction("AccessDenied", "Authorization");
}
}
internal T GetSafeData<T>(string url) {
using (var client = await _apiHttpClient.GetHttpClientAsync()) {
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode) {
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(content);
}
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
Throw New AuthenticationException("");
throw new Exception($"A problem happened while calling the API: {response.ReasonPhrase}");
}
}
You can sorta see how you might pass response to that same method, so you could do your AccessDenied redirect within that method as well and reduce your repetitive code everywhere.
It's a generic method, so you can use it for ANY call to that api. That should be enough to get you started. Hope it helps!
I'm currently using a DelegatingHandler to check requests if they become Unauthorized when sending to our Web API. If the response does become unauthorized, I'm currently sending a refresh token to log the user back in and then updating the following requests with the new access token. The issue that I'm running into, is that many of the calls are asynchronous and continue on before the other ones finish and the refresh token code is hit multiple times cause multiple refresh tokens to be updated/saved. What is the best way to handle this scenario? My current Handler looks like this..
public class AuthenticationHandler : DelegatingHandler
{
private AccountRepository _accountRepo;
private string _originalAuthToken = String.Empty;
private const int _maxRefreshAttempts = 1;
public AuthenticationHandler() : this(new HttpClientHandler())
{
_accountRepo = new AccountRepository();
}
protected AuthenticationHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage();
request = CheckForAuthToken(request);
response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
for (int i = 1; i == _maxRefreshAttempts; i++)
{
response = await _accountRepo.SignInWithRefreshToken();
if (response.IsSuccessStatusCode)
{
request = CheckForAuthToken(request);
response = await base.SendAsync(request, cancellationToken);
}
}
}
return response;
}
private HttpRequestMessage CheckForAuthToken(HttpRequestMessage request)
{
if (App.CurrentLoggedInUser != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", App.CurrentLoggedInUser.AccessToken);
}
return request;
}
}
I'm not sure if using a handler is best practice or ideal. I thought it would be nice to check every request just incase the access token becomes invalid during the call itself. What is the recommended approach when using refresh tokens? I am also using a DelegatingHandler to retry failed requests 2 times but the Authentication Handler is the last handler in the HttpClient pipeline. Any suggestions is greatly appreciated!
I'm moving some code from .net framework to .net standard and i'm struggling to replicate some code that creates a HttpClient.
private HttpClient CreateHttpClient(Guid userId, SiteHandler siteHandler)
{
List<DelegatingHandler> handlers = new List<DelegatingHandler>
{
new AccessTokenHandler(_authorisationService, userId)
};
HttpClient client = HttpClientFactory.Create(handlers.ToArray());
client.BaseAddress = _baseAddressUri;
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
public class AccessTokenHandler : DelegatingHandler
{
private readonly IAuthorisationService _authorisationService;
private readonly Guid _userId;
public AccessTokenHandler(IAuthorisationService authorisationService, Guid userId)
{
_authorisationService = authorisationService ?? throw new ArgumentNullException(nameof(authorisationService));
_userId = userId;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string token = await _authorisationService.GetValidTokenAsync(_userId);
if (token == null)
{
throw new ApiTokenException();
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
}
What this code does is it sets up some middleware on the request so that when a request is made using the HttpClient the AccessTokenHandler adds an Access Token for the user to the request at the time of the call.
I can't seem to find anything that allows me to do this using .net standard. I can't find HttpClientFactory outside of a .net framework project.
Can anyone help?