You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor客户端API实现2FA时缺失Identity.TwoFactorUserId Cookie问题

解决Blazor客户端2FA验证API调用失败问题

问题核心

TwoFactorSignInAsync方法依赖登录过程中生成的Identity.TwoFactorUserId Cookie关联用户会话,但Blazor客户端(尤其是WASM模式)默认不会自动处理Cookie的存储与携带,导致验证时找不到有效会话,调用失败。而Swagger在浏览器环境下自动处理Cookie,因此能正常验证。

可行解决方案

方案1:改用无状态会话验证(推荐,适配JWT架构)

放弃依赖Cookie,改用临时会话ID关联用户,具体步骤:

  1. 修改首次登录接口:用户用户名密码验证通过且需要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 });
        }
        // 其他登录结果处理...
    }
    
  2. 更新验证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("无效的验证码");
    }
    
  3. 更新DTO:在UserVerification类中添加SessionId字段,用于客户端传递会话标识。

方案2:强制Blazor客户端携带Cookie(适合Blazor Server或同源场景)

如果必须依赖Cookie,需确保客户端能正确存储并携带Cookie:

  1. 后端配置CORS允许凭证:

    builder.Services.AddCors(options =>
    {
        options.AddPolicy("AllowBlazor", policy =>
        {
            policy.WithOrigins("你的Blazor应用地址")
                  .AllowAnyHeader()
                  .AllowAnyMethod()
                  .AllowCredentials();
        });
    });
    
  2. 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默认共享浏览器会话,无需额外配置。

内容的提问来源于stack exchange,提问作者Kazoda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 16:33:34