Проверка подлинности Identity Framework, если подтвержден токен электронной почты
Можно ли проверить, истекает ли токен подтверждения для подтверждения, используя Identity Framework UserManager
? Независимо от того, какая ошибка, из следующего:
var result = await UserManager.ConfirmEmailAsync(userId, code);
Я получаю общую ошибку "Недопустимый токен".
Ответы
Ответ 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 не работает.