邮件确认后如何在原窗口跳转至对应页面?
问题:双因素认证邮件确认后,如何在原注册窗口打开登录页
我正在通过发送带确认链接的邮件实现双因素认证,邮件链接的模板代码如下:
<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共享状态,让原注册窗口自动检测验证状态并跳转:
- 注册页面添加轮询检测逻辑:
setInterval(() => { const isVerified = localStorage.getItem('emailVerified'); if (isVerified === 'true') { localStorage.removeItem('emailVerified'); window.location.href = '/UserAccount/Login'; } }, 3000); // 每3秒检查一次验证状态
- 修改
CompleteConfirmEmailAction,验证通过后设置状态并尝试关闭当前窗口:
[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
相关产品推荐
相关产品推荐

