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

RadZen Blazor应用邮件MFA验证后无法识别登录状态求助

RadZen Blazor应用邮件双因素认证(MFA)问题解决

问题背景

我要给RadZen创建的Blazor应用添加基于邮件验证令牌的双因素认证(MFA),已完成令牌输入页面,并扩展RadZen登录函数处理MFA逻辑:

public async Task<IActionResult> Login(string userName, string password, string redirectUrl)
{
    if (env.EnvironmentName == "Development" && userName == "admin" && password == "admin")
    {
        var claims = new List<Claim>()
        {
                new Claim(ClaimTypes.Name, "admin"),
                new Claim(ClaimTypes.Email, "admin")
        };

        roleManager.Roles.ToList().ForEach(r => claims.Add(new Claim(ClaimTypes.Role, r.Name)));
        await signInManager.SignInWithClaimsAsync(new ApplicationUser { UserName = userName, Email = userName }, isPersistent: false, claims);

        return Redirect($"~/{redirectUrl}");
    }

    if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
    {
        var user = await userManager.FindByNameAsync(userName);

        if (user == null)
        {
            return RedirectWithError("Invalid user or password", redirectUrl);
        }

        if (!user.EmailConfirmed)
        {
            return RedirectWithError("User email not confirmed", redirectUrl);
        }

        var result = await signInManager.PasswordSignInAsync(userName, password, false, true);

        if (result.Succeeded)
        {
            return Redirect($"~/{redirectUrl}");
        }
        else if (result.RequiresTwoFactor)
        {
            return await SendSecondFactorRequest(user, redirectUrl);
        }
    }

    return RedirectWithError("Invalid user or password", redirectUrl);
}

private async Task<IActionResult> SendSecondFactorRequest(ApplicationUser user, string redirectUrl)
{
    if (!user.TwoFactorEnabled)
    {
        return Redirect($"~/{redirectUrl}");
    }

    var token = await userManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultEmailProvider);
    this.mailService.SendToken(user.Email, token);
    return Redirect($"~/login-confirmation?Redirecturl={redirectUrl};User={user.Name}");
}

该逻辑会生成令牌并发送至用户邮箱,随后跳转至令牌输入页面。

遇到的问题

  1. 使用TwoFactorSignInAsync验证令牌时失败:
public async Task<IActionResult> LoginVerification(string user, string key, string redirectUrl)
{
    var result = await signInManager.TwoFactorSignInAsync(TokenOptions.DefaultEmailProvider, key, false, false);

    if (result.Succeeded)
    {
        var autentic = this.securetyService.IsAuthenticated();
        return Ok(redirectUrl);
    }

    return StatusCode(420, "Ungültiger Benutzername oder Verifikationsschlüssel.");
}
  1. 改用VerifyTwoFactorTokenAsync可验证令牌正确,但RadZen的SecurityService.IsAuthenticated()返回false,应用无法识别用户已登录:
public async Task<IActionResult> LoginVerification(string user, string key, string redirectUrl)
{
    if ((!string.IsNullOrEmpty(user)) && (!string.IsNullOrEmpty(key)))
    {
        var userInfo = await userManager.FindByNameAsync(user);

        var result = await userManager.VerifyTwoFactorTokenAsync(userInfo, TokenOptions.DefaultEmailProvider, key);
        
        if (result)
        {
            var autentic = this.securetyService.IsAuthenticated();
            return Ok(redirectUrl);
        }

    }
    return StatusCode(420, "Ungültiger Benutzername oder Verifikationsschlüssel.");
}

RadZen登录检查逻辑:

protected override async System.Threading.Tasks.Task OnInitializedAsync()
{
    Globals.PropertyChanged += OnPropertyChanged;
    await Security.InitializeAsync(AuthenticationStateProvider);
    if (!Security.IsAuthenticated())
    {
        UriHelper.NavigateTo("Login", true);
    }
    else
    {
        await Load();
    }
}

问题原因与解决方案

核心问题分析

  • TwoFactorSignInAsync依赖密码验证后的临时登录状态(PasswordSignInAsync返回RequiresTwoFactor时,SignInManager会生成临时Cookie标记该状态),你的跳转参数分隔符错误(用了;而非&)导致参数解析异常,且未确保临时状态有效。
  • VerifyTwoFactorTokenAsync仅验证令牌有效性,不会触发登录流程,因此不会创建用户认证会话,导致SecurityService.IsAuthenticated()返回false。

修改步骤

  1. 修复跳转参数格式
    修改SendSecondFactorRequest中的跳转URL,用&分隔参数并转义特殊字符:
private async Task<IActionResult> SendSecondFactorRequest(ApplicationUser user, string redirectUrl)
{
    if (!user.TwoFactorEnabled)
    {
        return Redirect($"~/{redirectUrl}");
    }

    var token = await userManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultEmailProvider);
    this.mailService.SendToken(user.Email, token);
    // 转义参数避免URL解析错误,用&分隔参数
    return Redirect($"~/login-confirmation?RedirectUrl={Uri.EscapeDataString(redirectUrl)}&User={Uri.EscapeDataString(user.UserName)}");
}
  1. 重写LoginVerification方法
    结合临时登录状态验证令牌,并在验证通过后手动完成登录:
public async Task<IActionResult> LoginVerification(string user, string key, string redirectUrl)
{
    // 优先从临时登录状态获取用户(PasswordSignInAsync返回RequiresTwoFactor后生成)
    var currentUser = await signInManager.GetTwoFactorAuthenticationUserAsync();
    
    // 临时状态失效时,通过用户名查找用户
    if (currentUser == null)
    {
        currentUser = await userManager.FindByNameAsync(user);
        if (currentUser == null)
        {
            return StatusCode(420, "无效的用户名或验证密钥。");
        }
    }

    // 验证令牌有效性
    bool tokenValid = await userManager.VerifyTwoFactorTokenAsync(currentUser, TokenOptions.DefaultEmailProvider, key);
    
    if (tokenValid)
    {
        // 手动创建用户认证会话,触发登录状态
        await signInManager.SignInAsync(currentUser, isPersistent: false);
        return Ok(redirectUrl);
    }

    return StatusCode(420, "无效的用户名或验证密钥。");
}
  1. 额外检查
    确保项目中已正确配置邮件令牌提供者(在Startup/Program.cs中):
services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    // 确保邮件令牌提供者已注册
    .AddTokenProvider<EmailTokenProvider<ApplicationUser>>(TokenOptions.DefaultEmailProvider);

原理说明

  • 手动调用SignInAsync会生成用户认证Cookie,RadZen的SecurityService依赖该Cookie识别登录状态。
  • 优先使用GetTwoFactorAuthenticationUserAsync获取临时状态用户,确保MFA流程的安全性(避免直接通过用户名关联令牌)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 04:52:01