JWT doesn't get stored in ASP.NET Core with Blazor

What am I missing for my user to be authenticated? Doesn't Microsoft.AspNetCore.Authentication.JwtBearer handle the client part (storing the token)?

The JwtBearer runs on server side , it will only validate the authorization header of request, namely Authorization: Bearer your_access_token, and won't care about how you WebAssembly codes runs . So you need send the request with a jwt accessToken . Since the tutorial suggests you should use localStorage , let's store the accessToken with localStorage .

Because WebAssembly has no access to BOM yet, we need some javascript codes served as glue . To do that, add a helper.js under the JwtAuthentication.Client/wwwroot/js/ :

var wasmHelper = {};

wasmHelper.ACCESS_TOKEN_KEY ="__access_token__";

wasmHelper.saveAccessToken = function (tokenStr) {
    localStorage.setItem(wasmHelper.ACCESS_TOKEN_KEY,tokenStr);
};

wasmHelper.getAccessToken = function () {
    return localStorage.getItem(wasmHelper.ACCESS_TOKEN_KEY);
};

And reference the script in your JwtAuthentication.Client/wwwroot/index.html

<body>
    <app>Loading...</app>
    <script src="js/helper.js"></script>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

Now, let's wrap the javascript codes into C# . Create a new file Client/Services/TokenService.cs:

public class TokenService
{
    public Task SaveAccessToken(string accessToken) {
        return JSRuntime.Current.InvokeAsync<object>("wasmHelper.saveAccessToken",accessToken);
    }
    public Task<string> GetAccessToken() {
        return JSRuntime.Current.InvokeAsync<string>("wasmHelper.getAccessToken");
    }
}

Register this service by :

// file: Startup.cs 
services.AddSingleton<TokenService>(myTokenService);

And now we can inject the TokenService into Login.cshtml and use it to save token :

@using JwtAuthentication.Client.Services
// ...
@page "/login"
// ...
@inject TokenService tokenService

// ...

@functions {
    public string Email { get; set; } = "";
    public string Password { get; set; } = "";
    public string Token { get; set; } = "";


    /// <summary>
    /// response from server
    /// </summary>
    private class TokenResponse{
        public string Token;
    }

    private async Task SubmitForm()
    {
        var vm = new TokenViewModel
        {
            Email = Email,
            Password = Password
        };

        var response = await Http.PostJsonAsync<TokenResponse>("http://localhost:57778/api/Token", vm);
        await tokenService.SaveAccessToken(response.Token);
    }
}

Let's say you want to send data within FetchData.cshtml

@functions {
    WeatherForecast[] forecasts;


    protected override async Task OnInitAsync()
    {
        var token = await tokenService.GetAccessToken();
        Http.DefaultRequestHeaders.Add("Authorization",String.Format("Bearer {0} ",token));
        forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
    }
}

and the result will be :

enter image description here


Apologies in advance as this is somewhat responding to a previous answer, but I don't have the rep to comment on that.

If it helps anyone else who was similarly looking for a solution to using JWT in a Blazor app, I found @itminus answer incredibly useful, but it also pointed me to another course.

One problem I found was that calling FetchData.cshtml a second time would blow up when it tries to add the Authorization header a second time.

Instead of adding the default header there, I added it to the HttpClient singleton after a successful login (which I believe Blazor creates for you automatically). So changing SubmitForm in Login.cshtml from @itminus' answer.

    protected async Task SubmitForm()
    {
        // Remove any existing Authorization headers
        Http.DefaultRequestHeaders.Remove("Authorization");

        TokenViewModel vm = new TokenViewModel()
        {
            Email = Email,
            Password = Password
        };

        TokenResponse response = await Http.PostJsonAsync<TokenResponse>("api/Token/Login", vm);

        // Now add the token to the Http singleton
        Http.DefaultRequestHeaders.Add("Authorization", string.Format("Bearer {0} ", response.Token));
    }

Then I realised, than as I'm building a SPA, so I didn't need to persist the token across requests at all - it's just in attached to the HttpClient.


The following class handle the login process on the client, storing the JWT token in local storage. Note: It is the developer responsibility to store the JWT token, and passes it to the server. The client (Blazor, Angular, etc.) does not do that for him automatically.

public class SignInManager
    {
        // Receive 'http' instance from DI
        private readonly HttpClient http;
        public SignInManager(HttpClient http)
        {
            this.http = http;
        }

        [Inject]
        protected LocalStorage localStorage;


        public bool IsAuthenticated()
        {
            var token = localStorage.GetItem<string>("token");

            return (token != null); 
        }

        public string getToken()
        {
            return localStorage.GetItem<string>("token");
        }

        public void Clear()
        {
            localStorage.Clear();
        }


        // model.Email, model.Password, model.RememberMe, lockoutOnFailure: false
        public async Task<bool> PasswordSignInAsync(LoginViewModel model)
        {
            SearchInProgress = true;
            NotifyStateChanged();

            var result = await http.PostJsonAsync<Object>("/api/Account", model);

            if (result)// result.Succeeded
           {
              _logger.LogInformation("User logged in.");

              // Save the JWT token in the LocalStorage
              // https://github.com/BlazorExtensions/Storage
              await localStorage.SetItem<Object>("token", result);


              // Returns true to indicate the user has been logged in and the JWT token 
              // is saved on the user browser
             return true;

           }

        }
    }

// This is how you call your Web API, sending it the JWT token for // the current user

public async Task<IList<Profile>> GetProfiles()
        {   
            SearchInProgress = true;
            NotifyStateChanged();

            var token = signInManager.getToken();
            if (token == null) {
                throw new ArgumentNullException(nameof(AppState)); //"No token";
            }

            this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

            // .set('Content-Type', 'application/json')
            // this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

            Profiles = await this.http.GetJsonAsync<Profile[]>("/api/Profiles");


            SearchInProgress = false;
            NotifyStateChanged();
        } 

// You also have to set the Startup class on the client as follows:

public void ConfigureServices(IServiceCollection services)
    {
        // Add Blazor.Extensions.Storage
       // Both SessionStorage and LocalStorage are registered
       // https://github.com/BlazorExtensions/Storage
       **services.AddStorage();**

      ...
    }

// Generally speaking this is what you've got to do on the client. // On the server, you've got to have a method, say in the Account controller, whose function is to generate the JWT token, you've to configure the JWT middleware, to annotate your controllers with the necessary attribute, as for instance:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]  

and so on...

Hope this helps...