确认电子邮件中的ASP.NET Core Identity无效令牌

时间:2016-08-05 04:46:22

标签: angularjs asp.net-core asp.net-identity forgot-password

这是一个非常类似的问题 aspnet identity invalid token on confirmation email 但解决方案无效,因为我使用的是包含ASP.NET核心标识的新ASP.NET Core 1.0。

我的方案如下:

  1. 在后端(ASP.NET Core)我有一个函数,它发送一个带有链接的密码重置电子邮件。为了生成该链接,我必须使用Identity生成代码。这样的事情。

    public async Task SendPasswordResetEmailAsync(string email)
    {
        //_userManager is an instance of UserManager<User>
        var userEntity = await _userManager.FindByNameAsync(email);
        var tokenGenerated = await _userManager.GeneratePasswordResetTokenAsync(userEntity);
        var link = Url.Action("MyAction", "MyController", new { email = email, code = tokenGenerated }, protocol: HttpContext.Request.Scheme);
         //this is my service that sends an email to the user containing the generated password reset link
         await _emailService.SendPasswordResetEmailAsync(userEntity , link);
    }
    

    这会生成一封电子邮件,其中包含以下链接:

    http://myapp:8080/passwordreset?code=CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX+5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu+jHX5cajptUBiVqIChiwoTODh7ei4+MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5+jebokKkR5HEJU+mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==

  2. 然后,我的AngularJs应用程序将显示一个带有输入和确认新密码的表单的视图,并使用新密码和从URL中的查询参数获得的代码输出JSON对象。

  3. 最后,我的后端将获得PUT请求,获取代码并使用Identity进行验证:

    [HttpPut]
    [AllowAnonymous]
    [Route("api/password/{email}")]
    public async Task<IActionResult> SendPasswordEmailResetRequestAsync(string email, [FromBody] PasswordReset passwordReset)
    {
        //some irrelevant validatoins here
        await _myIdentityWrapperService.ResetPasswordAsync(email, passwordReset.Password, passwordReset.Code);
        return Ok();
    }
    
  4. 问题是Identity以

    响应
      

    无效令牌

    错误。我发现问题是代码不匹配,上面的代码将在PUT请求的JSON对象中收到,如下所示:

    CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX 5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu jHX5cajptUBiVqIChiwoTODh7ei4 MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5 jebokKkR5HEJU mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==
    

    请注意,现在有+符号的位置有空格符号,这显然会导致Identity认为令牌不同。 出于某种原因,Angular正在以不同的方式解码URL查询参数。

    如何解决这个问题?

9 个答案:

答案 0 :(得分:16)

这个答案https://stackoverflow.com/a/31297879/2948212指出了我正确的方向。但正如我所说的那样是针对不同的版本,现在它的解决方案略有不同。

答案仍然是相同的:在base 64 url​​中编码令牌,然后在base 64 url​​中对其进行解码。这样,Angular和ASP.NET Core都将检索相同的代码。

我需要为Microsoft.AspNetCore.WebUtilities;

安装另一个依赖项

现在代码将是这样的:

public async Task SendPasswordResetEmailAsync(string email)
{
    //_userManager is an instance of UserManager<User>
    var userEntity = await _userManager.FindByNameAsync(email);
    var tokenGenerated = await _userManager.GeneratePasswordResetTokenAsync(userEntity);
    byte[] tokenGeneratedBytes = Encoding.UTF8.GetBytes(tokenGenerated);
    var codeEncoded = WebEncoders.Base64UrlEncode(tokenGeneratedBytes);
    var link = Url.Action("MyAction", "MyController", new { email = email, code = codeEncoded }, protocol: HttpContext.Request.Scheme);
     //this is my service that sends an email to the user containing the generated password reset link
     await _emailService.SendPasswordResetEmailAsync(userEntity , link);
}

并在PUT请求期间收回代码

[HttpPut]
[AllowAnonymous]
[Route("api/password/{email}")]
public async Task<IActionResult> SendPasswordEmailResetRequestAsync(string email, [FromBody] PasswordReset passwordReset)
{
    //some irrelevant validatoins here
    await _myIdentityWrapperService.ResetPasswordAsync(email, passwordReset.Password, passwordReset.Code);
    return Ok();
}

//in MyIdentityWrapperService
public async Task ResetPasswordAsync(string email, string password, string code)
{
    var userEntity = await _userManager.FindByNameAsync(email);
    var codeDecodedBytes = WebEncoders.Base64UrlDecode(code);
    var codeDecoded = Encoding.UTF8.GetString(codeDecodedBytes);
    await _userManager.ResetPasswordAsync(userEntity, codeDecoded, password);
}

答案 1 :(得分:3)

我已经尝试了上面的答案,但是这个guide帮助了我。基本上,您需要对代码进行编码,否则,您将遇到一些奇怪的错误。总结一下,您需要这样做:

string code = HttpUtility.UrlEncode(UserManager.GenerateEmailConfirmationToken(userID));

在此之后,如果适用,请解码代码:

string decoded = HttpUtility.UrlDecode(code)

答案 2 :(得分:1)

我有一个类似的问题,我正在编码我的令牌,但是它一直在失败的验证中,结果是这样的:options.LowercaseQueryStrings = true; 请勿在{{1​​}}上设置true,这会更改验证令牌的完整性,并且会出现“无效令牌错误”。

options.LowercaseQueryStrings

答案 3 :(得分:1)

在我的Asp.Net Core 3.0项目中设置ConfirmEmail页面后,我遇到了同样的问题。

OnGetAsync中的ConfirmEmail.cshtml.cs方法中删除以下行解决了该问题:

code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));

在支架式登录页面中,将code添加到callbackUrl,然后使用HtmlEncoder.Default.Encode(callbackUrl)对URL进行编码。单击链接后,解码自动完成,code就像确认电子邮件一样。

更新:

我注意到,在忘记密码过程中,code是经过Base64编码的,然后再放入callbackUrl中,这意味着Base64会解码 IS 必需。

因此,更好的解决方案是在将代码添加到callbackUrl之前,将以下行添加到生成代码的位置。

code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

这是已修复的issue的链接。

答案 4 :(得分:1)

我在 GCP 中使用 Cloud Run 托管我的网站时遇到了同样的问题,这里的所有解决方案都不适合我。

就我而言,问题出在本地存储在实例中的数据保护密钥。日志中的以下条目暗示了该问题:

<块引用>

将密钥存储在目录“/home/.aspnet/DataProtection-Keys”中,该目录可能不会在容器外持久化。容器销毁后,受保护的数据将不可用。

因此,令牌仅对发行它们的实例有效,当用户单击发送到其电子邮件的链接时,该实例已被销毁。

解决方案是对密钥使用分布式存储,例如,通过 EntityFramework 使用数据库:

using Microsoft.AspNetCore.DataProtection;
...
public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToDbContext<DbContext>();

    services.AddIdentity<User, IdentityRole>()
                .AddEntityFrameworkStores<AnotherDbContext>()
                .AddDefaultTokenProviders();
}

可以在here找到不同类型存储的详细信息。

答案 5 :(得分:0)

(根据这篇文章:https://stackoverflow.com/a/27943434/9869427

对于resetPasswordAsync(身份管理器)的“ 令牌无效”问题...,因为“ +”成为URL中的空格... 使用 Uri.EscapeUriString

例如:在我的sendResetPasswordByMailAsync

var token = "Aa+Bb Cc";
var encodedToken = Uri.EscapeDataString(token); 

encodedToken =“ Aa%20Bb2B%Cc”

var url = $"http://localhost:4200/account/reset-password?email={email}&token={encodedToken}";
var mailContent= $"Please reset your password by <a href='{url}'>clicking here</a>.";

现在,您可以单击您的链接,并且您将转到带有“ +”的良好网址(由%2B编码)...您的令牌将无效...

答案 6 :(得分:0)

ASP Core 2.1遇到了类似的问题,并且scratch不休,因为codetoken)的任何编码/解码都不适合我。而且我总是遇到userManager.ConfirmEmailAsync(user, code)

无效令牌错误

解决方案: 原来的问题是,不是用UserManager创建用户,而是用_dbContext.Users.AddAsync使用 dbcontext _userManager.CreateAsync来创建用户,对于我来说一切正常甚至没有对codetoken)进行任何编码/解码。

答案 7 :(得分:0)

就我而言,此问题是由于RegisterModel中的OnPostAsync方法编码了回调URL:

var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { userId = user.Id, code = code },
                    protocol: Request.Scheme);

await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                    $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

这种编码(HtmlEncoder.Default.Encode()在callbackUrl上的应用)使网址“&”成为“&”,从而使整个链接无效。

答案 8 :(得分:0)

您也可以在验证重置放置中的令牌时使用正则表达式。

var decode = token.Replace(" ", "+");

await _userManager.ResetPasswordAsync(user, decode, Password);