The issue: AuthState is always false, even after successful API auth and after HttpContext.SignInAsync.
I'm stuck with this as I don't understand why Blazor is not updating the authState.
Before .NET 8, I was using custom AuthProvider to update user:
public class WebAuthStateProvider(IHttpContextAccessor httpContextAccessor) : AuthenticationStateProvider, IAuthProvider{ private ClaimsPrincipal user = httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal(new ClaimsIdentity()); public override Task<AuthenticationState> GetAuthenticationStateAsync() { return Task.FromResult(new AuthenticationState(user)); } public async Task SaveUser(IEnumerable<Claim> claims) { var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); user = new(identity); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user))); } public async Task Logout() { user = new ClaimsPrincipal(new ClaimsIdentity()); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user))); } public void NotifyUserAuthentication() { NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); }}Login.razor:
private async Task LoginWithEmail(){ errorMessage = null; var loginRequest = new LoginRequestModel() { Platform = IsWeb ? "web" : "mobile", Email = email, Password = password }; try { var baseUrl = IsWeb ? Navigation.BaseUri : "https://mydomain/"; var requestUrl = $"{baseUrl}api/v1/auth/login"; var response = await Http.PostAsJsonAsync(requestUrl, loginRequest); if (response.StatusCode == HttpStatusCode.Unauthorized) { errorMessage = "Invalid email or password."; return; } if (!response.IsSuccessStatusCode) { errorMessage = "An error occurred while logging in. Please try again."; return; } var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>(); if (authResponse != null) { var claims = new List<Claim> { new(ClaimTypes.Email, authResponse.Email), new("Avatar", authResponse.Avatar), new("Id", authResponse.Id), new("JWT", authResponse.Token), }; claims.AddRange(authResponse.Roles.Select(role => new Claim(ClaimTypes.Role, role))); var test = AuthState.User; // always null (isAuthenticated = false) Navigation.NavigateTo("/feed", true); } } catch { errorMessage = "An unexpected error occurred. Please try again later."; }}Program.cs:
builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.LoginPath = "/identity/login"; options.LogoutPath = "/identity/logout"; options.AccessDeniedPath = "/identity/access-denied"; options.ExpireTimeSpan = TimeSpan.FromDays(7); options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.TokenValidationParameters = new() { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["AuthConfiguration:jwtTokenConfig:issuer"], ValidAudience = builder.Configuration["AuthConfiguration:jwtTokenConfig:issuer"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["AuthConfiguration:jwtTokenConfig:secret"]) ) }; }) .AddGoogle("Google", options => { options.ClientId = builder.Configuration["Google:ClientId"]; options.ClientSecret = builder.Configuration["Google:ClientSecret"]; options.ClaimActions.MapJsonKey("urn:google:profile", "link"); options.ClaimActions.MapJsonKey("urn:google:image", "picture"); options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always; options.SaveTokens = true; });builder.Services.AddAuthorization();builder.Services.AddCascadingAuthenticationState();builder.Services.AddHttpContextAccessor();...app.UseAuthentication();app.UseAuthorization();app.UseRouting();App.Razor - wrapped into CascadingAuthenticationState.
AuthController.cs:
[HttpPost("register")]public async Task<IActionResult> Register([FromBody] RegisterRequestModel requestModel){ if (await userManager.FindByEmailAsync(requestModel.Email) != null) return Conflict("User with this email already exists."); var user = new UserEntity { UserName = SanitizeUserName(requestModel.Name), Email = requestModel.Email, AvatarUrl = "default-avatar.png" }; var result = await userManager.CreateAsync(user, requestModel.Password); if (!result.Succeeded) return BadRequest(result.Errors); var roles = await userManager.GetRolesAsync(user); var token = GenerateJwtToken(user, roles); var claims = new List<Claim> { new(ClaimTypes.Email, user.Email), new("Avatar", user.AvatarUrl), new("Id", user.Id) }; claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role))); var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { IsPersistent = true }; await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new(claimsIdentity), authProperties); logger.LogInformation($"User registered: {requestModel.Name} {requestModel.Email}"); return Ok(new AuthResponse { Token = token, Email = user.Email, Avatar = user.AvatarUrl, Roles = roles.ToList(), Id = user.Id });}