1

For each API call in my App, I want to check whether the user has an expired JWT, and if so, I want to get a new one using a refresh token, and then proceed with the original request to API. This is supposed to all work in the background without the APP user experiencing any interruptions or need to login again.

I create my HttpClient like this:

static DelegatingHandler handler = new AuthenticationHandler();

static HttpClient httpClient = new HttpClient(handler)
{
    BaseAddress = new Uri("https://10.0.2.2:5001/api/v1")

};

AuthenticationHandler is a custom DelegatingHandler which has an override SendAsync method. Inside that method I check if request has status Unauthorised:

 if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)

And if it does, I need to send another request to my API with the currently owned JWT and Refresh tokens to generate new pair of tokens... Since this is an API call in the middle of another API call (as it all happens inside the custom DelegatingHandler which is a parameter for constructing my main HttpClient) - does refreshing the token needs to happen using a second HttpClient that I need to create literally to make the refresh token call?

I can't see how can I use the same HttpClient for this, how is this usually being done?


EDIT:

I can't see how I could use the same HttpClient for refreshToken call from inside AuthenticationHandler, as the handler is used to construct the HttpClient. Feels like a circular reference. I just have no idea how others do it in their code... I currently implemented it by using that second HttpClient which I only use for that one refreshToken call, and it works, but I have a feeling that there is a cleaner way to achieve this?

Btw, my (not refactored yet) SendAsync method inside AuthenticationHandler looks like this currently:

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    try
    {
        HttpResponseMessage response = new HttpResponseMessage();

        request = CheckForAuthToken(request);

        response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);

        if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
        {
            for (int i = 1; i == _maxRefreshAttempts; i++)
            {

                // Here I make a call to the API to refresh and return a new JWT, The authApiService uses a different HttpClient

                RefreshTokenRequestModel refreshTokenRequestModel = new RefreshTokenRequestModel
                {
                    Token = await SecureStorage.GetAsync("jwtToken"),
                    RefreshToken = await SecureStorage.GetAsync("refreshToken")
                };

                var apiResponse = await authApiService.RefreshToken(refreshTokenRequestModel);


                if (apiResponse.IsSuccessStatusCode)
                {
                    await SecureStorage.SetAsync("jwtToken", apiResponse.Content.Token);
                    await SecureStorage.SetAsync("refreshToken", apiResponse.Content.RefreshToken);

                    request = CheckForAuthToken(request);

                    response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
                }
            }
        }

        return response;
    }
    catch (Exception e)
    {

        throw e;
    }
    
}
Varin
  • 2,354
  • 2
  • 20
  • 37
  • Its generally undesirable to have two HTTPClients. Once you’ve received the response object, I don’t see any problem sending the next request. (Though I haven’t fully grasped whether your second call needs some different setup of the HTTPClient, re authentication.) Just be careful how you use async/await and/or tasks - see https://stackoverflow.com/a/32977930/199364. – ToolmakerSteve Nov 16 '21 at 17:59
  • @ToolmakerSteve AuthenticationHandler is passed to HttpClient constructor to create HttpClient. Hence I doubt I can make a call from AuthenticationHandler using the HttpClient that uses it during instantiation. Feels like a circular reference. I have to make the API call from inside the AuthenticationHandler as this is where I override the SendAsync method which checks every API call to see if the token is valid or needs refreshing. If it needs refreshing, It calls the API to get a new JWT token, and then continues making the original API call with that new token. – Varin Nov 16 '21 at 20:36
  • try to create your HttpClient object once and then use the same instance during the app life time – FabriBertani Nov 16 '21 at 23:05
  • 1
    @FabriBertani Well that's the whole issue - how can I use HttpClient inside AuthenticationHandler, if AuthenticationHandler is used to create HttpClient... – Varin Nov 16 '21 at 23:30
  • Why did you need to use HttpClient inside the handler ? – FabriBertani Nov 16 '21 at 23:38
  • After `response = await base.SendAsync`, the base code has completed a request/response cycle. While I'm not 100% certain, I believe you can now safely perform another `await base.SendAsync`. I would try it. If problems, then go ahead and make a second HTTPClient. Avoid calling anything in first client until second client has finished, and your `override SendAsync` has returned. CAVEAT: I haven't dealt with this situation, so I'm guessing. Bottom line is to not have BOTH clients SIMULTANEOUSLY doing something; but AFAIK you don't have to explicitly do anything to avoid conflicts. – ToolmakerSteve Nov 17 '21 at 00:01
  • @ToolmakerSteve I could try it. Since I'm using Refit for all other API calls I'd probably have to manually do a lot of things to convert the model to JSON, hardcode the route etc and construct HttpRequestMessage to do it here, right? New to Xamarin, did a lot of .NET MVC so not entirely sure. Just seems like a lot of manual work, using JsonSerializer and copying some methods that are already in HttpClient to create the HttpRequestMessage to then call SendAsync inside the Handler. But yeah, I can see it could work this way. If there is an easier way than what I described - hit me up pls ;) – Varin Nov 17 '21 at 00:45

0 Answers0