Подтвердить что ты не робот

Проверка подлинности Identity Framework, если подтвержден токен электронной почты

Можно ли проверить, истекает ли токен подтверждения для подтверждения, используя Identity Framework UserManager? Независимо от того, какая ошибка, из следующего:

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

Я получаю общую ошибку "Недопустимый токен".

4b9b3361

Ответ 1

Я нашел способ проанализировать токен за выданную дату, после чего вы можете проверить, находится ли он в разрешенном промежутке времени (по умолчанию 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
}

Я нашел этот блог и Нкозите ответ будет чрезвычайно полезным, и если вы хотите, чтобы пройти через исходный код идентификации, Microsoft имеет его здесь (предыдущие версии идентичности для MVC5 и ниже здесь). Кроме того, я приношу свои извинения, если он в плохом состоянии, чтобы ответить на вопрос, который вы сами наделите щедростью, но я не мог не продолжать искать лучшее решение.

Ответ 2

Я обойду это, сохранив/сохранив копию сгенерированного токена

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 SecurityStamp, so until that changes
        * (like when the user 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);
    }
}

Если токен совпадает с известным пользователем, который указывает, что он был действительно выпущенным токеном.

Затем попытается подтвердить токен с помощью User manager.

Если подтверждение не выполнено, токен истек, и предпринимается соответствующее действие.

Else, если токен подтвержден, он удаляется из связанного пользователя и, таким образом, отменяет повторное использование этого токена.

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;
}

Аналогичный подход был реализован и для сброса пароля.

Ответ 3

Адаптация решения для .NET Core 2.1, предоставляемая @Nkosi:

Класс 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 SecurityStamp, so until that changes
            * (like when the user 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);
    }
}

Обновление: CustomUserManager должен быть добавлен к сервисам в Startup.cs в методе ConfigureServices.

services.AddTransient<CustomUserManager>();

Без этого DependencyInjection не работает.