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!
Related
I have written a custom HttpClientHandler to set the token in each HTTP request and to handle 401 responses.
Here is the helper class.
public class CustomHttpClientHandler : HttpClientHandler
{
private readonly ISyncLocalStorageService localStorageService;
private readonly NavigationManager navigationManager;
public CustomHttpClientHandler(ISyncLocalStorageService localStorageService, NavigationManager NavigationManager)
{
this.localStorageService = localStorageService;
navigationManager = NavigationManager;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = localStorageService.GetItem<string>(Auth.Token.ToString());
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await base.SendAsync(request, cancellationToken);
if(response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
navigationManager.NavigateTo("login");
}
return response;
}
}
Now what I am trying to achieve is after navigating to the login page. I want this method to exit from here and not execute the return line.
What I know is I can throw a custom exception and catch it globally and then redirect from there. But just to know, is there any other way for it?
if(response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
navigationManager.NavigateTo("login"); // i want to exit code from here
}
return response; // and not want to return response.
What's happening is even though I redirect it to the login page it returns the response and my razor component will throw null reference exception because of the below line, as I am reading the response and returning it after deserializing it, which returns null.
var stringResult = await httpResult.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TOutput>(stringResult);
What I can do is to check everywhere after getting an HTTP response for null, but I want to handle it generically.
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.
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}"));
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?