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 original SendAsync 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.


  • And then the murders began.

    I don't see why you can't just call base.SendAsync() twice, once for the reauth and once for the actual message send.


  • Banned

    @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.


  • Considered Harmful

    @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!


Log in to reply