Ovis
Ovis

Reputation: 391

Refresh token on asp.net web api and Blazor server side

I have an application where the backend is an asp.net web api and the front-end is a Blazor server side. Both projects are using net6.0.

I have implemented jwt token authentication, so users can register and login from the front-end.

My problem is that if the user refreshes a page, he automatically gets logged out. My understanding is that this can be solved using refresh token (I'm not sure if this understanding is correct).

I have tried to follow this guide: Refresh Token with Blazor WebAssembly and ASP.NET Core Web API However since I'm using Blazor server side I cannot intercept HTTP Requests using the approach in the article.

My question is: in my Blazor server side application how can I prevent users automatically getting logged out due to page refresh and how can I intercept the http request?

UPDATE: Notice I already have everything working in regards to token and authentication between the back and frontend. The part that I'm missing is inside the blazor server side application in the program.cs file. I basically want to intercept all http request and call a method. In program.cs I have:

builder.Services.AddScoped<IRefreshTokenService, RefreshTokenService>();

I want RefreshTokenService to be called on every http request. I have tried creating a middleware (which calls the RefreshTokenService), inside the program.cs like:

app.UseMyMiddleware();

But this only get called once.

Upvotes: 0

Views: 3551

Answers (1)

Michal Diviš
Michal Diviš

Reputation: 2206

Here's a very simplified version of an API client I'm using in my app that's also split into an ASP.NET Core API backend and a Blazor Server frontend.

The way it works is that the accessToken gets retreived from local storage and added as an authentication header to the HttpRequestMessage in my API client before each API call.

MyApiClient.cs

public class MyApiClient
{
    private readonly IHttpClientFactory _clientFactory;
    private readonly IMyApiTokenProvider _myApiTokenProvider;

    public MyApiClient(IHttpClientFactory clientFactory, IMyApiTokenProvider myApiTokenProvider)
    {
        _clientFactory = clientFactory;
        _myApiTokenProvider = myApiTokenProvider;
    }

    public async Task<ApiResponse<CustomerListResponse>> GetCustomersAsync()
    {
        //create HttpClient
        var client = _clientFactory.CreateClient("MyApiHttpClient");

        //create HttpRequest
        var request = CreateRequest(HttpMethod.Get, "/getCustomers");

        //call the API
        var response = await client.SendAsync(request);

        //if Unauthorized, refresh access token and retry
        if(response.StatusCode == HttpStatusCode.Unauthorized)
        {
            var refreshResult = await RefreshAccessToken(client);

            if (refreshResult.IsSuccess)
            {
                //save new token
                await _backendTokenProvider.SetAccessToken(refreshResult.NewAccessToken);

                //create request again, with new access token
                var retryRequest = await CreateRequest(HttpMethod.Get, "/getCustomers");

                //retry
                response = await client.SendAsync(retryRequest);
            }
            else
            {
                //refresh token request failed
                return ApiResponse<CustomerListResponse>.Error("Token invalid");
            }
        }

        //parse response
        var customers = await response.Content.ReadFromJsonAsync<ApiResponse<CustomerListResponse>>();

        return customers;
    }

    private HttpRequestMessage CreateRequest<TRequest>(string command, HttpMethod method, TRequest requestModel = null) where TRequest : class
    {
        //create HttpRequest
        var request = new HttpRequestMessage(method, command);

        //add body if not empty
        if (requestModel is not null)
        {
            request.Content = JsonContent.Create(requestModel);
        }

        //set the Auth header to the Access Token value taken from Local Storage
        var accessToken = await _myApiTokenProvider.GetAccessToken();
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        return request;
    }

    private async Task<ApiResponse<RefreshTokenResponse>> RefreshAccessToken(HttpClient client)
    {
        var refreshToken = await _backendTokenProvider.GetRefreshToken();

        if (refreshToken is null)
        {
            return ApiResponse<RefreshTokenResponse>.Error("Refresh token is null, cannot refresh access token");
        }

        var refreshRequest = CreateRequest(HttpMethod.Post, "/refreshToken", new RefreshTokenRequest(refreshToken));

        var refreshResponse = await client.SendAsync(refreshRequest);

        var refreshResult = await response.Content.ReadFromJsonAsync<ApiResponse<RefreshTokenResponse>>();

        return refreshResult;
    }
}

MyApiTokenProvider.cs

public class MyApiTokenProvider : IMyApiTokenProvider
{
    private readonly ProtectedLocalStorage _protectedLocalStorage;

    public MyApiTokenProvider(ProtectedLocalStorage protectedLocalStorage)
    {
        _protectedLocalStorage = protectedLocalStorage;
    }

    public async Task<string> GetAccessToken()
    {
        var result = await _protectedLocalStorage.GetAsync<string>("accessToken");
        return result.Success ? result.Value : null;
    }

    public async Task<string> GetRefreshToken()
    {
        var result = await _protectedLocalStorage.GetAsync<string>("refreshToken");
        return result.Success ? result.Value : null;
    }

    public async Task SetAccessToken(string newAccessToken)
    {
        await _protectedLocalStorage.SetAsync("accessToken", newAccessToken);
    }

    public async Task SetRefreshToken(string newRefreshToken)
    {
        await _protectedLocalStorage.SetAsync("refreshToken", newRefreshToken);
    }
}

Upvotes: 1

Related Questions