.NET 8 Blazor Server API认证异常:AuthState始终为False
问题:API认证成功且调用
HttpContext.SignInAsync后,Blazor的AuthState始终为False 在Blazor应用中,API认证流程已成功执行,后端也调用HttpContext.SignInAsync写入了认证Cookie,但前端的AuthState始终显示未认证(IsAuthenticated = false),AuthState.User始终为null。此前.NET 8版本前,使用自定义WebAuthStateProvider更新用户状态,但当前该方式未生效。
相关代码片段
自定义WebAuthStateProvider
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; // 始终为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();
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 }); }
核心原因分析
- 自定义AuthStateProvider未替换默认实现:Blazor默认使用
ServerAuthenticationStateProvider(服务器端)或RemoteAuthenticationStateProvider(WASM),若未将自定义WebAuthStateProvider注册为AuthenticationStateProvider的实现,系统会忽略自定义逻辑,默认Provider不会自动感知后端Cookie变化。 - 前端未触发状态更新:登录/注册接口成功后,前端未调用自定义Provider的更新方法,导致
AuthState保持旧的未认证状态。 - Server-Side Blazor的请求级上下文限制:
HttpContext是请求级别的,自定义Provider初始化时仅读取一次用户信息,后续认证变化无法自动同步。
解决方案
1. 正确注册自定义Provider
在Program.cs中替换默认的AuthenticationStateProvider:
// 注册自定义Provider为AuthenticationStateProvider的实现 builder.Services.AddScoped<AuthenticationStateProvider, WebAuthStateProvider>(); // 同时注册IAuthProvider接口 builder.Services.AddScoped<IAuthProvider>(sp => sp.GetRequiredService<AuthenticationStateProvider>() as WebAuthStateProvider);
2. 修改前端登录逻辑,触发状态更新
在Login.razor的LoginWithEmail方法中,调用自定义Provider的SaveUser方法同步用户状态:
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))); // 调用自定义Provider更新用户状态 await AuthProvider.SaveUser(claims); // 此时AuthState已更新 var test = (await AuthState).User; Navigation.NavigateTo("/feed", true); }
需在
Login.razor中注入依赖:[Inject] private IAuthProvider AuthProvider { get; set; } [CascadingParameter] private Task<AuthenticationState> AuthState { get; set; }
3. 优化自定义Provider(Server-Side Blazor)
修改GetAuthenticationStateAsync方法,每次请求读取最新的HttpContext用户信息:
public class WebAuthStateProvider(IHttpContextAccessor httpContextAccessor) : AuthenticationStateProvider, IAuthProvider { private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; private ClaimsPrincipal? _cachedUser; public override Task<AuthenticationState> GetAuthenticationStateAsync() { var user = _cachedUser ?? _httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal(new ClaimsIdentity()); return Task.FromResult(new AuthenticationState(user)); } public async Task SaveUser(IEnumerable<Claim> claims) { var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); _cachedUser = new(identity); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(_cachedUser))); } public async Task Logout() { _cachedUser = new ClaimsPrincipal(new ClaimsIdentity()); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(_cachedUser))); } public void NotifyUserAuthentication() { _cachedUser = _httpContextAccessor.HttpContext?.User; NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } }
4. 修正中间件顺序
Program.cs中中间件必须遵循以下顺序:
app.UseRouting(); app.UseAuthentication(); // 必须在UseAuthorization之前 app.UseAuthorization(); // 其他中间件
内容的提问来源于stack exchange,提问作者Rdq
相关产品推荐
相关产品推荐

