C# .NET, making a conditional HTTP request in case of a HTTP request.
-
So, yeah, that title. Not the most confusing statement I've ever written but I guess it's up there.
Let me explain what I want to achieve: I've got a .NET Core Server running which is wired to use JWTs for authentication. Said JWTs are short-lived so I'm also handing over a refresh token for the client to be able to request a new JWT in case it expires. Sort of like OpenID does it but since both server and client are under my control, I don't need all that back and forth - I also don't have to deal with redirections and the nightmare of external webviews in mobile clients.
The JWT, once obtained, is then attached to every HTTP request from the client to the server like so:
builder.Services.AddSingleton<JwtAuthenticationStateProvider>(); builder.Services.AddSingleton<AuthenticationStateProvider>(provider => provider.GetRequiredService<JwtAuthenticationStateProvider>()); builder.Services.AddScoped(provider => new JwtTokenMessageHandler(appUri, provider.GetRequiredService<JwtAuthenticationStateProvider>())); builder.Services.AddHttpClient("Foo.ServerApi", client => client.BaseAddress = appUri).AddHttpMessageHandler<JwtTokenMessageHandler>(); builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Foo.ServerApi"));
Now, the
JwtTokenMessageHandler
simply modifies the originalSendAsync
function and attaches the JWT to the header as a bearer token, so that every GET/PUT/... made to the server is automatically authenticated should the called endpoint require it:protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var uri = request.RequestUri; var isSelfApiAccess = (uri is not null) ? _allowedBaseAddress.IsBaseOf(uri) : false; if (isSelfApiAccess) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _loginStateService.Token ?? string.Empty); } return base.SendAsync(request, cancellationToken); }
Now, what I'd like to do in this function is to check whether the JWT expired and if so, request a new one. For that I'd have to call the
/api/RefreshToken
endpoint.Doing that "inside" an already running request seems like a bad idea (aside from potential infinite recursions), so I'm unsure how to best go about that. Should I conjure up a second HTTP client? Or simply create a wrapper for HTTP requests which first does the check and then consecutively runs the original request? Or is that a bad idea altogether and I should do something else?
Why JWTs? Because I then also get
Roles
and stuff automatically.
-
I don't see why you can't just call
base.SendAsync()
twice, once for the reauth and once for the actual message send.
-
@Rhywden said in C# .NET, making a conditional HTTP request in case of a HTTP request.:
Or simply create a wrapper for HTTP requests which first does the check and then consecutively runs the original request?
I'd go with this.
-
@Gustav said in C# .NET, making a conditional HTTP request in case of a HTTP request.:
@Rhywden said in C# .NET, making a conditional HTTP request in case of a HTTP request.:
Or simply create a wrapper for HTTP requests which first does the check and then consecutively runs the original request?
I'd go with this.
I wouldn't. Unless all the endpoints you call need the same authz. At least you want to be able to wrap different requests differently.
I've solved this problem in the past by putting the auth stuff outside the request, in repository base functionality. Also it's really annoying that I don't get smacked in the face for framework support for this pattern, given that JWT consumers all need this.
Another option is to spin up async timed refreshes, of course - that's probably less cluttered, but it's less Functional.
-
@Rhywden I've used IdentityModel's OidcClient in the past. It has a RefreshTokenDelegatingHandler that refreshes the token when needed during the SendAsync call. Looks a lot like what you're wanting to do. See https://github.com/IdentityModel/IdentityModel.OidcClient/blob/main/src/OidcClient/RefreshTokenDelegatingHandler.cs
-
@robo2 That does indeed look a lot like what I want to achieve. Thanks!