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

邮件确认后如何在原窗口跳转至对应页面?

问题:双因素认证邮件确认后,如何在原注册窗口打开登录页

我正在通过发送带确认链接的邮件实现双因素认证,邮件链接的模板代码如下:

<a class="confirm-button" href="@url">Confirm</a>

生成邮件内容时会替换链接地址:

string body = GetConfirmationMailBody();
body = body.Replace("@url", $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}/UserAccount/CompleteConfirmEmail/?{ModelToQueryString(model)}");

该链接指向控制器的CompleteConfirmEmail Action,通过自定义令牌提供者验证用户验证码,验证通过后需要跳转到Login Action,且要求在用户注册时打开的原窗口中显示登录视图,但目前点击邮件链接总是打开新的浏览器窗口。

我的控制器核心代码如下:

[HttpGet]
public async Task<IActionResult> CompleteConfirmEmail(RegisterViewModel model)
{
    string action = "Register";
    if(!string.IsNullOrWhiteSpace(model?.EmailVerificationCode))
    {
        BankApiUser user = await UserManager.FindByEmailAsync(model?.Email);
        if(user != null && await UserManager.VerifyTwoFactorTokenAsync(user, Startup.Startup.TwoFactorTokenProviderName, model.EmailVerificationCode))
        {
            model.EmailConfirmationState = ConfirmationState.Complete;
            model.Submitted = false;
            action = "Login";
        }
    }
    if(model == null)
    {
        model = new RegisterViewModel();
    }
    string url = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}/UserAccount/{action}/?{model.ToQueryString()}";
    string target = "BankApi.Register";
    return RedirectToAction(action, model);
}

[HttpGet]
public IActionResult Login(RegisterViewModel? regModel = null)
{
    LoginViewModel model = new LoginViewModel(regModel);
    return View(model);
}

[HttpGet]
public IActionResult Login(LoginViewModel model)
{
    return View(model);
}

我曾尝试在注册页面通过JS设置窗口名称:

window.name = "BankApi.Register";

然后修改CompleteConfirmEmail Action返回JS脚本,尝试指定窗口打开,但该方法无效:

[HttpGet]
public async Task<IActionResult> CompleteConfirmEmail(RegisterViewModel model)
{
    string action = "Register";
    if(!string.IsNullOrWhiteSpace(model?.EmailVerificationCode))
    {
        BankApiUser user = await UserManager.FindByEmailAsync(model?.Email);
        if(user != null && await UserManager.VerifyTwoFactorTokenAsync(user, Startup.Startup.TwoFactorTokenProviderName, model.EmailVerificationCode))
        {
            model.EmailConfirmationState = ConfirmationState.Complete;
            model.Submitted = false;
            action = "Login";
        }
    }
    if(model == null)
    {
        model = new RegisterViewModel();
    }
    string url = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}/UserAccount/{action}/?{model.ToQueryString()}";
    string target = "BankApi.Register";
    return Content(@$"
        <script>
            try
            {{
                window.close();
                window.open({url}, {target});
            }}
            catch(e)
            {{
                console.error(e);
                throw e;
            }}
        </script>", 
        "text/html");
}

可行解决方案

方案1:修改邮件链接的target属性

在邮件模板的<a>标签中添加target="_self",强制链接在当前窗口打开,但注意部分邮件客户端或浏览器设置可能会忽略该属性:

<a class="confirm-button" href="@url" target="_self">Confirm</a>

方案2:利用同域localStorage实现原窗口自动跳转

由于邮件链接打开的窗口和原注册窗口属于同域,可以通过localStorage共享状态,让原注册窗口自动检测验证状态并跳转:

  1. 注册页面添加轮询检测逻辑:
setInterval(() => {
  const isVerified = localStorage.getItem('emailVerified');
  if (isVerified === 'true') {
    localStorage.removeItem('emailVerified');
    window.location.href = '/UserAccount/Login';
  }
}, 3000); // 每3秒检查一次验证状态
  1. 修改CompleteConfirmEmail Action,验证通过后设置状态并尝试关闭当前窗口:
[HttpGet]
public async Task<IActionResult> CompleteConfirmEmail(RegisterViewModel model)
{
    bool isVerified = false;
    if(!string.IsNullOrWhiteSpace(model?.EmailVerificationCode))
    {
        BankApiUser user = await UserManager.FindByEmailAsync(model?.Email);
        if(user != null && await UserManager.VerifyTwoFactorTokenAsync(user, Startup.Startup.TwoFactorTokenProviderName, model.EmailVerificationCode))
        {
            model.EmailConfirmationState = ConfirmationState.Complete;
            model.Submitted = false;
            isVerified = true;
        }
    }
    if(model == null)
    {
        model = new RegisterViewModel();
    }

    if (isVerified)
    {
        // 设置验证状态到localStorage,通知原注册窗口跳转
        return Content(@"
            <script>
                localStorage.setItem('emailVerified', 'true');
                // 尝试关闭当前窗口(仅对通过window.open打开的窗口有效)
                if (window.opener) {
                    window.close();
                } else {
                    // 如果无法关闭,直接在当前窗口跳转登录页
                    window.location.href = '/UserAccount/Login';
                }
            </script>", "text/html");
    }
    else
    {
        return RedirectToAction("Register", model);
    }
}

方案3:简化流程,直接在链接打开的窗口跳转

放弃控制原注册窗口,验证通过后直接在当前打开链接的窗口跳转到登录页,这是最稳定的方案,只需保留原RedirectToAction逻辑即可:

[HttpGet]
public async Task<IActionResult> CompleteConfirmEmail(RegisterViewModel model)
{
    string action = "Register";
    if(!string.IsNullOrWhiteSpace(model?.EmailVerificationCode))
    {
        BankApiUser user = await UserManager.FindByEmailAsync(model?.Email);
        if(user != null && await UserManager.VerifyTwoFactorTokenAsync(user, Startup.Startup.TwoFactorTokenProviderName, model.EmailVerificationCode))
        {
            model.EmailConfirmationState = ConfirmationState.Complete;
            model.Submitted = false;
            action = "Login";
        }
    }
    if(model == null)
    {
        model = new RegisterViewModel();
    }
    return RedirectToAction(action, model);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 20:14:52