Blazor WASM and Refit token - c#

I am using refit in my blazor wasm application, I want to set the token in AuthorizationHeaderValueGetter, the api to which I connect is not written in .net. but I have registered refit in the program.cs
builder.Services.AddRefitClient<IApi>(settings).ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri("Address");
})
do I have to create a DelegatingHandler for this?

public class AuthHeaderHandler : DelegatingHandler
{
private readonly ILocalStorageService _localStorageService;
public AuthHeaderHandler(ILocalStorageService localStorageService)
{
_localStorageService = localStorageService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var auth = request.Headers.Authorization;
if (auth != null)
{
if (await _localStorageService.ContainKeyAsync("Token"))
{
string token = await _localStorageService.GetItemAsync<string>("Token");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
}
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
}

Related

Request ended prematurely using own Request

I'm using the packages "FastEndpoints" & "FastEndpoints.Security" for creating the RESTApi.
This is my Endpoint:
public class LoginEndpoint : Endpoint<LoginRequest, LoginResponse>
{
IUserService _userSvc;
public LoginEndpoint(IUserService users)
{
_userSvc = users;
}
public override void Configure()
{
Verbs(Http.GET);
Routes("/api/login");
AllowAnonymous();
}
public override async Task HandleAsync(LoginRequest req, CancellationToken ct)
{
if (_userSvc.LoginValidByName(req.Name, req.Password))
{
var user = _userSvc.GetFromName(req.Name);
var expiresAt = DateTime.UtcNow.AddDays(1);
var token = JWTBearer.CreateToken(
GlobalSettings.TOKEN_SIGNING_KEY,
expiresAt,
user.Permissions.Select(p => p.Name));
await SendAsync(
new LoginResponse()
{
Token = token,
ExpiresAt = expiresAt
});
}
else
{
await SendUnauthorizedAsync();
}
}
}
Using Postman, the endpoints works as expected:
But when using RestSharp (and mind you, I'm very new to the whole RESTApi world), I get an error 'Request ended prematurely'.
This is my simple call:
public class ApiClient
{
private RestClient _restClient;
public ApiClient(string baseUrl)
{
_restClient = new RestClient(baseUrl);
//ServicePointManager.ServerCertificateValidationCallback += (s, c, ch, p) => true;
}
public async Task<bool> UserValid(string username, string password)
{
var request = new RestRequest("/api/login", Method.Get);
request.AddParameter("name", username);
request.AddParameter("password", password);
var result = await _restClient.GetAsync(request);
if (result.StatusCode == HttpStatusCode.OK)
return true;
else
return false;
}
}
Can someone fill me in?
Since it works with Postman, I suspect my call being bad.
Is:
_userSvc.LoginValidByName
Or any other function missing an await by chance?

.net services.AddHttpClient Automatic Access Token Handling

I am trying to write a Blazor app that uses client secret credentials to get an access token for the API. I wanted to encapsulate it in such a way that it handles the token fetching and refreshing behind the scenes. To achieve this, I created the following inherited class which uses IdentityModel Nuget package:
public class MPSHttpClient : HttpClient
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger Logger;
public MPSHttpClient(IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
public async Task<bool> RefreshTokens()
{
if (Tokens.RefreshToken == null)
return false;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
RefreshToken = Tokens.RefreshToken
});
Logger.LogInformation("Refresh Token Result {0}", result.IsError);
if (result.IsError)
{
Logger.LogError("Error: {0)", result.ErrorDescription);
return false;
}
Tokens.RefreshToken = result.RefreshToken;
Tokens.AccessToken = result.AccessToken;
Logger.LogInformation("Access Token: {0}", result.AccessToken);
Logger.LogInformation("Refresh Token: {0}" , result.RefreshToken);
return true;
}
public async Task<bool> CheckTokens()
{
if (await RefreshTokens())
return true;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
public new async Task<HttpResponseMessage> GetAsync(string requestUri)
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
var response = await base.GetAsync(requestUri);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
response = await base.GetAsync(requestUri);
}
}
return response;
}
}
The idea is to keep from having to write a bunch of redundant code to try the API, then request/refresh the token if you are unauthorized. I tried it at first using extension methods to HttpClient, but there was no good way to inject the Configuration into a static class.
So my Service code is written as this:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly MPSHttpClient _httpClient;
public EngineListService(MPSHttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
Everything compiles great. In my Startup, I have the following code:
services.AddScoped<TokenProvider>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Just to be complete, Token Provider looks like this:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
When I run the App, it complains that it can't find a suitable constructor for EngineListService in the call to services.AddHttpClient. Is there a way to pass AddHttpClient an actual instance of the IEngineListService. Any other way I might be able to achieve this?
Thanks,
Jim
I think that EngineListService should not be registered as a HttpClient in services and instead you should register MPSHttpClient.
This follows the "Typed Client" example in the documentation and uses IHttpClientFactory behind the scenes.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#typed-clients
When you use services.AddHttpClient the constructor needs a HttpClient parameter. That is how the HttpClientFactory initializes the HttpClient and then passes it into your service ready to go.
You can change your MPSHttpClient to not inherit HttpClient and instead add a HttpClient parameter to the constructor. You could also have it implement an interface like IMPSHttpClient
public class MPSHttpClient
{
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
HttpClient = httpClient;
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
}
You must remove these lines from MPSHttpClient and use the injected client.
// remove this
var client = new HttpClient();
In Startup add
services.AddHttpClient<MPSHttpClient>(client =>
{
// add any configuration
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Change EngineListService to a normal service registration as it is not a HttpClient
services.AddScoped<IEngineListService, EngineListService>()
Special thanks to #pinkfloydx33 for helping me solve this. This link that he shared https://blog.joaograssi.com/typed-httpclient-with-messagehandler-getting-accesstokens-from-identityserver/ was everything I needed. The trick was that there exists a class called DelegatingHandler that you can inherit and override the OnSendAsync method and do all of your token-checking there before sending it to the final HttpHandler. So my new MPSHttpClient class is as so:
public class MPSHttpClient : DelegatingHandler
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger<MPSHttpClient> Logger;
private readonly HttpClient client;
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger<MPSHttpClient> logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
client = httpClient;
}
public async Task<bool> CheckTokens()
{
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBearerToken(Tokens.AccessToken);
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
request.SetBearerToken(Tokens.AccessToken);
response = await base.SendAsync(request, cancellationToken);
}
}
return response;
}
}
The big changes here are the inheritance and I used DI to obtain the HttpClient much like #Rosco mentioned. I had tried to override OnGetAsync in my original version. When inheriting from DelegatingHandler, all you have to override is OnSendAsync. This will handle all of your get, put, post, and deletes from your HttpContext all in one method.
My EngineList Service is written as if there were no tokens to be considered, which was my original goal:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly HttpClient _httpClient;
public EngineListService(HttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
The Token Provider stayed the same. I plan to add expirations and such to it, but it works as is:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
The ConfigureServices code changed just a bit:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<TokenProvider>();
services.AddTransient<MPSHttpClient>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
...
}
You instantiate MPSHttpClient as Transient, then reference it with the AddHttpMessageHandler call attached to the AddHttpClient call. I know this is different than how others implement HttpClients, but I learned this method of creating client services from a Pluralsight video and have been using it for everything. I create a separate Service for each entity in the database. If say I wanted to do tires, I would add the following to ConfigureServices:
services.AddHttpClient<ITireListService, TireListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
It will use the same DelegatingHandler so I can just keep adding services for each entity type while no longer worrying about tokens. Thanks to everyone that responded.
Thanks,
Jim

How to register generic interfaces in http client?

In my aspnet core project I am using HttpClient, I have generic interface and class as well, but I can not register them in startup.
My interface looks like:
public interface IHttpClientWrapper<T> where T : class
{
Task<T> GetAsync(string url, string authType, string token, CancellationToken cancellationToken);
}
My class look like:
public class HttpClientWrapper<T> : IHttpClientWrapper<T> where T : class
{
private readonly HttpClient _client;
public HttpClientWrapper(HttpClient client)
{
_client = client;
}
public async Task<T> GetAsync(string url, string authType, string token,
CancellationToken cancellationToken)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authType, token);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
using (var response = await _client.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken))
{
var stream = await response.Content.ReadAsStreamAsync();
response.EnsureSuccessStatusCode();
return stream.ReadAndDeserializeFromJson<T>();
}
}
}
What I would like to achieve:
services.AddHttpClient<IHttpClientWrapper<T>, HttpClientWrapper<T>>()
.ConfigurePrimaryHttpMessageHandler(handler =>
new HttpClientHandler
{
AutomaticDecompression = System.Net.DecompressionMethods.GZip
});
What I tried and it was failed:
services.AddTransient(typeof(IHttpClientWrapper<>),typeof(HttpClientWrapper<>)).ConfigureOptions(
new HttpClientHandler
{
AutomaticDecompression = System.Net.DecompressionMethods.GZip
});
In short I need to somehow register AutomaticDecompression = System.Net.DecompressionMethods.GZip it can be with Transient if possible
I'm not sure why you registering IHttpClientWrapper. If it's transient, then you can you can just create it when you need it.
If you have a good reason to use DI for it then you could make the class non-generic and make the method generic:
public interface IHttpClientWrapper
{
Task<T> GetAsync<T>(string url, string authType, string token, CancellationToken cancellationToken);
}

Why is IHttpContextAccessor.HttpContext sometimes null?

Using .Net Core 2.2, I'm using HttpClient to call an API, along with an Http Message Handler. The message handler injects IHttpContextAccessor, but that object sometimes contains an HttpContext property that is null - I can't understand why that would ever be null.
Here's the service registration in startup:
services.AddTransient<HttpClientTokenHandler>();
services.AddHttpClient("client", c =>
{
c.BaseAddress = new Uri(options.ApiUrl.TrimEnd('/') + '/');
}).AddHttpMessageHandler<HttpClientTokenHandler>();
services.AddHttpContextAccessor();
And the token handler implementation where HttpContext is sometimes null.
public HttpClientTokenHandler(IHttpContextAccessor context)
{
_context = context;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var access_token = await _context.HttpContext.GetTokenAsync(Constants.TokenTypes.AccessToken);
if (!string.IsNullOrEmpty(access_token))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", access_token);
}
var response = await base.SendAsync(request, cancellationToken);
return response;
}
What could cause _context.HttpContext to be null, when it's simply being injected?

Setting request headers per request with IHttpClientFactory

I'm using typed clients with IHttpClientFactory. Like this:
// Startup.cs
services.AddHttpClient<MyHttpClient>()
// MyHttpClient.cs
public class MyHttpClient
{
public MyHttpClient(HttpClient client)
{
Client = client;
}
public HttpClient Client { get; }
}
// MyService.cs
public class MyService {
public MyService(MyHttpClient httpClient) {}
public async Task SendRequestAsync(string uri, string accessToken) {
_httpClient.Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
await _httpClient.Client.GetAsync(uri);
}
}
I'm unsure how this works. Will the request headers be set only for this request, or for every subsequent request that is made using this instance of httpClient. How can I set header on a per request basis?
You can use an DelegatingHandler to add an header to each request the HttpClient will make.
public class HeaderHandler: DelegatingHandler
{
public HeaderHandler()
{
}
public HeaderHandler(DelegatingHandler innerHandler): base(innerHandler)
{
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("CUSTOM-HEADER","CUSTOM HEADER VALUE");
return await base.SendAsync(request, cancellationToken);
}
}
}
You register the hanlder using :
service.AddTransient<HeaderHandler>()
.AddHttpClient<MyHttpClient>()
.AddHttpMessageHandler<HeaderHandler>();

Categories