Identity Framework测试确认电子邮件令牌是否已过期

时间:2017-03-23 09:10:24

标签: asp.net-mvc asp.net-identity email-confirmation

是否可以使用Identity Framework的UserManager来测试确认电子邮件令牌是否已过期?无论错误是什么,请参阅以下内容:

var result = await UserManager.ConfirmEmailAsync(userId, code);

我收到通用的“无效令牌”错误。

4 个答案:

答案 0 :(得分:6)

我找到了一种解析发布日期令牌的方法,然后您可以检查该令牌是否在允许的时间范围内(如果未指定,则默认为24小时)。

Identity.cs

ApplicationUserManager

public IDataProtector Protector { get; set; }

public TimeSpan TokenLifespan { get; set; }

ApplicationUserManager Create()

// Explicitly set token expiration to 24 hours. 
manager.TokenLifespan = TimeSpan.FromHours(24);
var dataProtectionProvider = options.DataProtectionProvider;
manager.Protector = dataProtectionProvider.Create("ASP.NET Identity");

if (dataProtectionProvider != null)
{
    manager.UserTokenProvider =
        new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"))
        {
            TokenLifespan = manager.TokenLifespan
        };
}

AccountController.cs

public async Task<ActionResult> ConfirmEmail(string Code, string UserId)
{
// Try/catch, validation, etc.
var tokenExpired = false;
var unprotectedData = UserManager.Protector.Unprotect(Convert.FromBase64String(Code));
var ms = new MemoryStream(unprotectedData);
using (BinaryReader reader = new BinaryReader(ms))
{
    var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero);
    var expirationTime = creationTime + UserManager.TokenLifespan;
    if (expirationTime < DateTimeOffset.UtcNow)
    {
        tokenExpired = true;
    }
 }
 // Do something if token is expired, else continue with confirmation
}

我发现this blog post和Nkosi的回答非常有帮助,如果您要查看Identity源代码,Microsoft会提供here(MVC5和更低版本的{{ 3}})。另外,如果您回答的问题很糟糕,请问您自己对自己的赏金有何要求,但我忍不住道歉,但我不禁继续寻找更好的解决方案。

答案 1 :(得分:5)

我通过保留/存储所生成令牌的副本来解决这个问题

public class ApplicationUser : IdentityUser {
    public string EmailConfirmationToken { get; set; }
    public string ResetPasswordToken { get; set; }
}

,并将其与派生的UserManager<ApplicationUser>中的用户相关联。

public override async System.Threading.Tasks.Task<string> GenerateEmailConfirmationTokenAsync(string userId) {
    /* NOTE:
        * The default UserTokenProvider generates tokens based on the users's SecurityStamp, so until that changes
        * (like when the user's password changes), the tokens will always be the same, and remain valid. 
        * So if you want to simply invalidate old tokens, just call manager.UpdateSecurityStampAsync().
        */
    //await base.UpdateSecurityStampAsync(userId);

    var token = await base.GenerateEmailConfirmationTokenAsync(userId);
    if (!string.IsNullOrEmpty(token)) {
        var user = await FindByIdAsync(userId);
        user.EmailConfirmationToken = token; //<<< Last issued token
        //Note: If a token is generated then the current email is no longer confirmed.
        user.EmailConfirmed = false;
        await UpdateAsync(user);
    }
    return token;
}

提供令牌进行确认后,便会通过令牌搜索用户。

public static class ApplicationUserManagerExtension {
    public static Task<string> FindIdByEmailConfirmationTokenAsync(this UserManager<ApplicationUser> manager, string confirmationToken) {
        string result = null;
        ApplicationUser user = manager.Users.SingleOrDefault(u => u.EmailConfirmationToken != null && u.EmailConfirmationToken == confirmationToken);
        if (user != null) {
            result = user.Id;
        }
        return Task.FromResult(result);
    }
}

如果令牌与已知用户匹配,表明该用户是有效发行的令牌。

然后将尝试与用户管理器确认令牌。

如果确认失败,则令牌已过期,并采取了适当的措施。

否则,如果令牌已确认,则会将其从关联用户中删除,从而使该令牌的重用无效。

public override async System.Threading.Tasks.Task<IdentityResult> ConfirmEmailAsync(string userId, string token) {
    var user = await FindByIdAsync(userId);
    if (user == null) {
        return IdentityResult.Failed("User Id Not Found");
    }
    var result = await base.ConfirmEmailAsync(userId, token);
    if (result.Succeeded) {
        user.EmailConfirmationToken = null;
        return await UpdateAsync(user);
    } else if (user.EmailConfirmationToken == token) {
        //Previously Issued Token expired
        result = IdentityResult.Failed("Expired Token");
    }
    return result;
}

也采用了类似的方法来重置密码。

答案 2 :(得分:0)

这里是@Nkosi提供的解决方案的 .NET Core 2.1 改编版:

ApplicationUser类

public class ApplicationUser : IdentityUser 
{
    public string EmailConfirmationToken { get; set; }
    public string ResetPasswordToken { get; set; }
}

派生的UserManager类

public class CustomUserManager : UserManager<ApplicationUser>
{
    public CustomUserManager(IUserStore<ApplicationUser> store, 
        IOptions<IdentityOptions> optionsAccessor, 
        IPasswordHasher<ApplicationUser> passwordHasher, 
        IEnumerable<IUserValidator<ApplicationUser>> userValidators, 
        IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, 
        ILookupNormalizer keyNormalizer, 
        IdentityErrorDescriber errors, 
        IServiceProvider services, 
        ILogger<UserManager<ApplicationUser>> logger) 
        : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
    {

    }

    public override async Task<string> GenerateEmailConfirmationTokenAsync(ApplicationUser user)
    {
        /* NOTE:
            * The default UserTokenProvider generates tokens based on the users's SecurityStamp, so until that changes
            * (like when the user's password changes), the tokens will always be the same, and remain valid. 
            * So if you want to simply invalidate old tokens, just call manager.UpdateSecurityStampAsync().
            */
        //await base.UpdateSecurityStampAsync(userId);

        var token = await base.GenerateEmailConfirmationTokenAsync(user);
        if (!string.IsNullOrEmpty(token))
        {
            user.EmailConfirmationToken = token; //<<< Last issued token
            //Note: If a token is generated then the current email is no longer confirmed.
            user.EmailConfirmed = false;
            await UpdateAsync(user);
        }
        return token;
    }

    public override async Task<IdentityResult> ConfirmEmailAsync(ApplicationUser user, string token)
    {
        if (user == null)
        {
            return IdentityResult.Failed(new IdentityError {Description = "User not found."});
        }
        var result = await base.ConfirmEmailAsync(user, token);
        if (result.Succeeded)
        {
            user.EmailConfirmationToken = null;
            return await UpdateAsync(user);
        }
        else if (user.EmailConfirmationToken == token)
        {
            //Previously Issued Token expired
            result = IdentityResult.Failed(new IdentityError { Description = "Expired token." });
        }
        return result;
    }

}

UserManager扩展

public static class ApplicationUserManagerExtension
{
    public static Task<string> FindIdByEmailConfirmationTokenAsync(this UserManager<ApplicationUser> manager, string confirmationToken)
    {
        string result = null;

        ApplicationUser user = manager.Users
            .SingleOrDefault(u => u.EmailConfirmationToken != null && u.EmailConfirmationToken == confirmationToken);

        if (user != null)
        {
            result = user.Id;
        }
        return Task.FromResult(result);
    }
}

更新: 必须在ConfigureServices方法的Startup.cs中将CustomUserManager添加到服务。

services.AddTransient<CustomUserManager>();

否则,DependencyInjection将失败。

答案 3 :(得分:0)

你可以使用我的控制器。它是工作伙伴。

        public IActionResult ForgotPassword()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> ForgotPassword(string Email)
        {
            if (string.IsNullOrEmpty(Email))
            {
                return View();
            }

            var user = await _userManager.FindByEmailAsync(Email);
            if (user == null)
            {
                return View();
            }

            var code =await _userManager.GeneratePasswordResetTokenAsync(user);

            var callback = Url.Action("ResetPassword", "Account", new
            {
                token=code,
            },Request.Scheme);

            // send email
            await _emailSender.SendEmailAsync(Email, "Confirm Password Reset", $"<a href='{callback}'>If you want to reset your password click please !</a>");

            return RedirectToAction("ForgotPasswordConfirmation", "Account");

        }


        public IActionResult ForgotPasswordConfirmation() => View();


        public IActionResult ResetPassword(string token)
        {
            if (token == null)
            {
                return View();
            }

            var model = new ResetPasswordModel()
            {
                Token = token,
            };
            return View(model);
        }

        [HttpPost]
        public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                return RedirectToAction("Index", "Home");
            }

            var result = await _userManager.ResetPasswordAsync(user, model.Token, model.Password);

            if (result.Succeeded)
            {
                return RedirectToAction("ResetPasswordConfirmation", "Account");
            }

            return View(model);
        }

        public ActionResult ResetPasswordConfirmation() => View();