【问题标题】:ASP.NET Core Identity invalid token on confirmation email确认电子邮件上的 ASP.NET Core Identity 无效令牌
【发布时间】:2016-12-11 09:36:52
【问题描述】:

这是一个与此非常相似的问题 aspnet identity invalid token on confirmation email 但解决方案无效,因为我使用的是包含 ASP.NET Core Identity 的新 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();
    }
    

问题是 Identity 以一个

响应

令牌无效

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

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

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

如何解决?

【问题讨论】:

  • 在我的情况下(Asp.Net Core 3.0),脚手架页面似乎引入了这个错误。请在此处查看我的answer

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


【解决方案1】:

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

答案还是一样的:在base 64 url​​中编码token,然后在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);
}

【讨论】:

  • 这也适用于验证电子邮件帐户。
  • 谢谢!顺便说一句,您可以考虑使用HttpPatch 动词修饰SendPasswordEmailResetRequestAsync,因为用户将部分更新而不是完全更新PUT 所在的位置。
  • 我有这个确切的设置,但由于提到的字符,我仍然偶尔会得到一个无效的令牌。即'..... M4pHg6/s0Kydq/Sa9/2WI2ypq/OJgY+fyhgXzvb ....'
【解决方案2】:

我遇到了类似的问题,我正在对我的令牌进行编码,但它一直未能通过验证,问题竟然是这样的:options.LowercaseQueryStrings = true; 不要在options.LowercaseQueryStrings 上设置true,这会改变验证令牌的完整性,您将收到无效令牌错误。

// This allows routes to be in lowercase
services.AddRouting(options =>
{
     options.LowercaseUrls = true;
      options.LowercaseQueryStrings = false;
});

【讨论】:

    【解决方案3】:

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

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

    在此之后,如果它适用于你,解码代码:

    string decoded = HttpUtility.UrlDecode(code)
    

    【讨论】:

    • 谢谢老兄,太棒了,太优雅了。这个答案应该是最重要的,工作正常!
    【解决方案4】:

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

    ConfirmEmail.cshtml.cs 中的OnGetAsync 方法中删除以下行修复了问题:

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

    在搭建的登录页面中,code 被添加到callbackUrl,然后使用HtmlEncoder.Default.Encode(callbackUrl) 对 URL 进行编码。当点击链接时,解码会自动完成,code 应该是确认电子邮件。

    更新:

    我注意到,在 忘记密码 过程中,code 在放入 callbackUrl 之前经过 Base64 编码,这意味着 Base64 解码 IS 是必要的。

    因此,更好的解决方案是将以下行添加到生成代码的任何位置,然后再将其添加到 callbackUrl。

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

    这里是issue 的链接,已修复。

    【讨论】:

    • 我也注意到了这个问题我脚手架后...只是脚手架的代码,还是我错过了发生在我之前的这个错误搭建这段代码?
    • 我也不确定。测试将是创建一个空白项目,测试链接,搭建页面并再次测试链接。我会尝试并报告。
    • @marno11,我尝试使用空白项目重现该问题,但似乎问题已得到解决。空白项目的确认和重置密码链接在脚手架之前和脚手架之后都有效,文件已经有 Base64UrlEncode 行,正是我的回答建议添加它的位置。
    • 这里是issue,确实已经修复了。
    • 这对我的问题代码进行了排序 = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));谢谢
    【解决方案5】:

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

    对于resetPasswordAsync(identity manager)“token invalid”问题...因为“+”变成了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】:

      我在 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找到。

      【讨论】:

        【解决方案7】:

        对于 ASP Core 2.1 也有类似的问题,并且让我摸不着头脑,因为 code(token) 的任何编码/解码都对我不起作用。对于userManager.ConfirmEmailAsync(user, code),我总是遇到Invalid token错误


        解决方案: 原来,问题是用户不是用UserManager创建的,而是使用像_dbContext.Users.AddAsync这样的dbcontext在用_userManager.CreateAsync替换这个创建方法之后,即使没有任何编码/解码,一切对我来说都很好对于code(token)。

        【讨论】:

          【解决方案8】:

          在我的情况下,这个问题是由于 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的应用),使得url '&'变成了'&',从而使整个链接失效。

          【讨论】:

            【解决方案9】:

            在reset put中验证令牌时也可以使用正则表达式。

            var decode = token.Replace(" ", "+");
            
            await _userManager.ResetPasswordAsync(user, decode, Password);
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-01-06
              • 1970-01-01
              • 2015-12-13
              • 2018-03-19
              • 2020-11-08
              • 2016-06-07
              • 2015-10-08
              • 2020-04-11
              相关资源
              最近更新 更多