Hi Kinga, I had a test in my side, but everything worked well, please allow me to share my sample with you. I created a new .net 8 blazor web app with InteractiveServer render mode. Then I followed this document to add custom authentication state provider for my blazor application. In the meantime, I also added jwt token logic into it to simulate your requirement.
My Program.cs, like what you can see, I injected Blazored.LocalStorage
service.
using Blazored.LocalStorage;
using BlazorWebAppCustomAuth;
using BlazorWebAppCustomAuth.Components;
using Microsoft.AspNetCore.Components.Authorization;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
builder.Services.AddBlazoredLocalStorage();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
And here's my custom auth state provider.
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace BlazorWebAppCustomAuth
{
public class CustomAuthStateProvider : AuthenticationStateProvider
{
private readonly ILocalStorageService _localStorageService;
private ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());
private AuthenticationState _cachedAuthState;
public CustomAuthStateProvider(ILocalStorageService localStorageService)
{
_localStorageService = localStorageService;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorageService.GetItemAsync<string>("authToken");
if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(_anonymous);
}
else {
var handler = new JwtSecurityTokenHandler();
var tokenContent = handler.ReadJwtToken(savedToken);
var claims = tokenContent.Claims;
var identitify = claims.Where(x => x.Type == "name").FirstOrDefault().Value;
var identity = new ClaimsIdentity(
[
new Claim(ClaimTypes.Name, identitify),
], "Custom Authentication");
var user = new ClaimsPrincipal(identity);
return await Task.FromResult(new AuthenticationState(user));
}
}
public async Task AuthenticateUserAsync(string userIdentifier)
{
var identity = new ClaimsIdentity(
[
new Claim(ClaimTypes.Name, userIdentifier),
], "Custom Authentication");
var token = generateJwt(userIdentifier);
await _localStorageService.SetItemAsync("authToken", token);
var user = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(user)));
}
public string generateJwt(string userIdentifier)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Name, userIdentifier),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("role","admin")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"));
var token = new JwtSecurityToken(
issuer: "Test.com",
audience: "Test.com",
expires: DateTime.Now.AddHours(3),
claims: claims,
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
var accessToken = new JwtSecurityTokenHandler().WriteToken(token);
return accessToken;
}
}
}
In App.razor component, I changed the Routes component to be non-prerendering by <Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />
otherwise I will get InvalidOperationException on line var savedToken = await _localStorageService.GetItemAsync<string>("authToken");
because JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle when prerendering is enabled.
Here's my Routes.razor
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
@* <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" /> *@
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
</Found>
</Router>
And I created a Login.razor component to authenticate user. The SignIn
method will trigger the
AuthenticateUserAsync
to store the token. And after signing in, I can refresh the page to trigger the GetAuthenticationStateAsync
method to check the jwt token.
@page "/login"
@inject AuthenticationStateProvider AuthenticationStateProvider
@rendermode InteractiveServer
<input @bind="userIdentifier" />
<button @onclick="SignIn">Sign in</button>
<AuthorizeView>
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
</Authorized>
<NotAuthorized>
<p>You're not authorized.</p>
</NotAuthorized>
</AuthorizeView>
@code {
public string userIdentifier = string.Empty;
private void SignIn()
{
((CustomAuthStateProvider)AuthenticationStateProvider).AuthenticateUserAsync(userIdentifier);
}
}
Everything worked well in my side.
If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.
Best regards,
Tiny