asp.net-coreblazorblazor-server-sideblazor-webassemblyblazor-webapp

Jwt Authentication is not working in Blazor Web Assembly (Server and Client)


I am using Blazor web assembly bother server side and web assembly. I am trying to add JWT authentication but it is not working. When I route to InteractiveServer or InteractiveAssembly pages with [Authorize] attribute, I am seeing not found pages in the web browser. If I remove that attribute, everything works fine.

Goal: The goal is to authenticate using JWT. I am using CustomAuthenticationStateProvider which is returning nothing to mimic no user is authenticated. I have no problem with that. I think my configurations to add Authentication and Authorization is missing something.

Here are my Program.cs for both projects:

Server side Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.FluentUI.AspNetCore.Components;
using Microsoft.IdentityModel.Tokens;
using OctuFit.Web.Client.ApiServices;
using OctuFit.Web.Client.Services;
using OctuFit.Web.Client.Utils;
using OctuFit.Web.Components;
using Refit;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

builder.Services.AddFluentUIComponents();

builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = "asdas",
        ValidAudience = "asdasd",
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("asdasdasd"))
    };
});
builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();

builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
builder.Services.AddScoped<IAuthService, AuthService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseAntiforgery();

app.MapStaticAssets();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(OctuFit.Web.Client._Imports).Assembly);

app.Run();

Web assembly Program.cs

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.FluentUI.AspNetCore.Components;
using OctuFit.Web.Client.ApiServices;
using OctuFit.Web.Client.Services;
using OctuFit.Web.Client.Utils;
using Refit;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddFluentUIComponents();

builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddAuthorizationCore();

builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
builder.Services.AddScoped<IAuthService, AuthService>();

// Refit Services
builder.Services.AddScoped<RefitInterceptor>();
builder.Services.AddRefitClient<IApiAuthService>()
    .ConfigureHttpClient(c =>
    {
        c.BaseAddress = new Uri(builder.Configuration["Api:BaseUrl"] ?? throw new ArgumentNullException("Api base url is null."));
    })
    .AddHttpMessageHandler<RefitInterceptor>();

await builder.Build().RunAsync();

If I don't specify AddJwtBearer so it says that the "Authentication scheme is not provided".

Question:

  1. Why we need to specify AddJwtBearer, although we don't need to verify the JWT as I am providing CustomAuthenticationStateProvider.
  2. Am I adding correct services to the container?
  3. Am I missing something?

Any help will be highly appreciated. Or any tutorial to see how authentication and authorization actually works.

Thank you

Update 1: So after reading and researching a lot. I came across some intresting things. The Blazor auth pipelines does not working like server. I am defining my own CustomAuthenticationStateProvider so I don't need "AddAuthentication" and "UseAuthentication, UseAuthorization" in the pipeline. So I have removed

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = "asdas",
        ValidAudience = "asdasd",
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("asdasdasd"))
    };
});

and

app.UseAuthentication();
app.UseAuthorization();

The new issue I am facing now is, when I use [Authorize], this exception is happening:

System.InvalidOperationException: Unable to find the required 'IAuthenticationService' service. Please add all the required services by calling 'IServiceCollection.AddAuthentication' in the application startup code.

Any help here?


Solution

  • So what I did this is after searching for so long, created another class

    public class AuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
    {
        public Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
        {
            return next(context);
        }
    }
    

    Injected it

    builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>();
    

    And everything started to work. Can someone please explain me what is happening?