Blazor客户端API实现2FA时缺失Identity.TwoFactorUserId Cookie问题
解决Blazor客户端2FA验证API调用失败问题
问题核心
TwoFactorSignInAsync方法依赖登录过程中生成的Identity.TwoFactorUserId Cookie关联用户会话,但Blazor客户端(尤其是WASM模式)默认不会自动处理Cookie的存储与携带,导致验证时找不到有效会话,调用失败。而Swagger在浏览器环境下自动处理Cookie,因此能正常验证。
可行解决方案
方案1:改用无状态会话验证(推荐,适配JWT架构)
放弃依赖Cookie,改用临时会话ID关联用户,具体步骤:
修改首次登录接口:用户用户名密码验证通过且需要2FA时,生成临时会话ID,存储到缓存(如Redis)并关联用户ID,返回会话ID给客户端。
[HttpPost("login")] public async Task<IActionResult> Login([FromBody] LoginDto model) { var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, false, lockoutOnFailure: false); if (result.RequiresTwoFactor) { var user = await _userManager.FindByNameAsync(model.Username); var sessionId = Guid.NewGuid().ToString(); // 缓存会话ID,设置15分钟过期 await _redis.SetStringAsync($"2fa_session:{sessionId}", user.Id.ToString(), TimeSpan.FromMinutes(15)); return Ok(new { RequiresTwoFactor = true, SessionId = sessionId }); } // 其他登录结果处理... }更新验证API逻辑:通过客户端传递的会话ID获取用户,使用
VerifyTwoFactorTokenAsync直接验证验证码,不再依赖Cookie会话。[HttpPost("verification")] public async Task<IActionResult> LoginWithOTP([FromBody] UserVerification model) { // 从缓存获取用户ID var userId = await _redis.GetStringAsync($"2fa_session:{model.SessionId}"); if (string.IsNullOrEmpty(userId)) { return BadRequest("会话已过期或无效"); } var user = await _userManager.FindByIdAsync(userId); if (user == null) { return BadRequest("用户不存在"); } // 验证OTP验证码 var isValid = await _userManager.VerifyTwoFactorTokenAsync(user, "Email", model.Code); if (isValid) { // 生成JWT令牌(原逻辑保留) var roles = await _userManager.GetRolesAsync(user); var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.UserName) }; foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role)); } var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]!)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var expiry = DateTime.Now.AddDays(Convert.ToInt32(_configuration["JWT:ExpiryInDays"])); var token = new JwtSecurityToken( _configuration["JWT:ValidIssuer"], _configuration["JWT:ValidAudience"], claims, expires: expiry, signingCredentials: creds ); // 删除已使用的会话ID await _redis.DeleteKeyAsync($"2fa_session:{model.SessionId}"); return Ok(new UserLoginResult { Successful = true, Token = new JwtSecurityTokenHandler().WriteToken(token) }); } return BadRequest("无效的验证码"); }更新DTO:在
UserVerification类中添加SessionId字段,用于客户端传递会话标识。
方案2:强制Blazor客户端携带Cookie(适合Blazor Server或同源场景)
如果必须依赖Cookie,需确保客户端能正确存储并携带Cookie:
后端配置CORS允许凭证:
builder.Services.AddCors(options => { options.AddPolicy("AllowBlazor", policy => { policy.WithOrigins("你的Blazor应用地址") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); });Blazor客户端配置请求携带凭证:
- WASM模式下,发起请求时设置
BrowserRequestCredentials.Include:var request = new HttpRequestMessage(HttpMethod.Post, "api/auth/verification"); request.Content = JsonContent.Create(verificationModel); request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include); var response = await _httpClient.SendAsync(request); - Server模式下,HttpClient默认共享浏览器会话,无需额外配置。
- WASM模式下,发起请求时设置
内容的提问来源于stack exchange,提问作者Kazoda
相关产品推荐
相关产品推荐

