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.
Related
public class DateObsessedHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestDate = request.Headers.Date;
// do something with the date ...
var response = await base.SendAsync(request, cancellationToken);
// if(response.statuscode is 403)
// How do I redirect?
return response;
}
I've tried the delegation handler above.
How do I redirect to a controller Action?
As far as I know, if you get the response from httpclient in your application, it will not directly redirect to another page. You still need write some codes to handle the response and return a new context from your application.
Normally, we could firstly throw the exception in the DateObsessedHandler and then we could use ExceptionHandler middleware to handle the exception and redirect to another page.
More details, you could refer to below codes:
public class DateObsessedHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestDate = request.Headers.Date;
// do something with the date ...
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Forbidden)
{
throw new Exception("403");
}
// if(response.statuscode is 403)
// How do I redirect?
return response;
}
}
Startup.cs Configure method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error.InnerException.Message == "403")
{
context.Response.Redirect("http://www.google.com");
}
});
});
// other middleware
}
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'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 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 developing a Web API 2 application and I'm currently trying to format error resposnes in a uniform way (so that the consumer will also know what data object/structure they can inspect to get more info about the errors). This is what I've got so far:
{
"Errors":
[
{
"ErrorType":5003,
"Message":"Error summary here",
"DeveloperAction":"Some more detail for API consumers (in some cases)",
"HelpUrl":"link to the docs etc."
}
]
}
This works fine for exceptions thrown by the application itself (i.e inside controllers). However, if the user requests a bad URI (and gets a 404) or uses the wrong verb (and gets a 405) etc, Web Api 2 spits out a default error message e.g.
{
Message: "No HTTP resource was found that matches the request URI 'http://localhost/abc'."
}
Is there any way of trapping these kinds of errors (404, 405 etc.) and formatting them out into the error response in the first example above?
So far I've tried:
Custom ExceptionAttribute inherting ExceptionFilterAttribute
Custom ControllerActionInvoker inherting ApiControllerActionInvoker
IExceptionHandler (new Global Error Handling feature from Web API 2.1)
However, none of these approaches are able to catch these kinds of errors (404, 405 etc). Any ideas on how/if this can be achieved?
...or, am I going about this the wrong way? Should I only format error responses in my particular style for application/user level errors and rely on the default error responses for things like 404?
You can override the DelegatingHandler abstract class and intercept the response to the client. This will give you the ability to return what you want.
Here's some info on it.
http://msdn.microsoft.com/en-us/library/system.net.http.delegatinghandler(v=vs.118).aspx
Here's a poster of the Web Api pipeline that shows what can be overriden.
http://www.asp.net/posters/web-api/asp.net-web-api-poster.pdf
Create a Handler class like this to override the response
public class MessageHandler1 : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine("Process request");
// Call the inner handler.
var response = base.SendAsync(request, cancellationToken);
Debug.WriteLine("Process response");
if (response.Result.StatusCode == HttpStatusCode.NotFound)
{
//Create new HttpResponseMessage message
}
;
return response;
}
}
In your WebApiConfig.cs class add the handler.
config.MessageHandlers.Add(new MessageHandler1());
UPDATE
As Kiran mentions in the comments you can use the OwinMiddleware to intercept the response going back to the client. This would work for MVC and Web Api running on any host.
Here's an example of how to get the response and change it as it goes to the client.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(MyMiddleware));
}
}
public class MyMiddleware : OwinMiddleware
{
public MyMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
if(context.Response.StatusCode== 404)
{
context.Response.StatusCode = 403;
context.Response.ReasonPhrase = "Blah";
}
}
}
I have done in same way as #Dan H mentioned
public class ApiGatewayHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.NotFound)
{
var objectContent = response.Content as ObjectContent;
return await Task.FromResult(new ApiResult(HttpStatusCode.NotFound, VmsStatusCodes.RouteNotFound, "", objectContent == null ? null : objectContent.Value).Response());
}
return response;
}
catch (System.Exception ex)
{
return await Task.FromResult(new ApiResult(HttpStatusCode.BadRequest, VmsStatusCodes.UnHandledError, ex.Message, "").Response());
}
}
}
Added routing like below and now it hits the try catch for invalid url
config.Routes.MapHttpRoute(name: "DefaultApi",routeTemplate: "api/{controller}/{id}",defaults: new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(name: "NotFound", routeTemplate: "api/{*paths}", defaults: new { controller = "ApiError", action = "NotFound" });