Reputation: 1101
I'm trying to implement a invitation system where newly created accounts received a confirmation token by email using UserManager.GenerateEmailConfirmationToken and UserManager.ConfirmEmail. However the ConfirmEmail method is always returning false.
As per suggestion in this post I implemented my MachineKeyDataProtector and registered in Unity like this:
container.RegisterType<IUserTokenProvider<User, Guid>,
DataProtectorTokenProvider<User, Guid>>(new InjectionConstructor(new MachineKeyDataProtector("ASP.NET Identity")));
My UserManager is injected with the IUserTokenProvider like this:
public UserManager(IUserStore<User, Guid> store, IUserTokenProvider<User, Guid> userTokenProvider)
: base(store)
{
// Configure validation logic for passwords
this.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
this.UserTokenProvider = userTokenProvider;
}
I then encapsulated the GenerateEmailConfirmationToken method in a extension method:
public static string GenerateUrlForEmailConfirmationToken(this UserManager<User, Guid> userManager, UrlHelper urlHelper, Guid userId)
{
var verificationCode = userManager.GenerateEmailConfirmationToken(userId);
var url = urlHelper.Link(string.Empty, new
{
controller = "Account",
action = "Verify",
userId = userId,
verificationCode = verificationCode
});
return url;
}
And I'm using it:
var url = UserManager.GenerateUrlForEmailConfirmationToken(Url, targetUser.Id);
//injects the url in a ready made html template
NotificationService.NotifyNewUser(targetUser, url);
Debugging GenerateUrlForEmailConfirmationToken I saw that MachineKeyDataProtector's protect method is being called, however MachineKeyDataProtector's unprotect is never called by UserManager's ConfirmEmail.
If I change the DI configuration to register the IUserTokenProvider per request
container.RegisterType<IUserTokenProvider<User, Guid>,
DataProtectorTokenProvider<User, Guid>>(new PerRequestLifetimeManager(),
new InjectionConstructor(new MachineKeyDataProtector("ASP.NET Identity")));
Then the MachineKeyDataProtector's unprotect is called but it trows a CryptographicException with the following message "Error occurred during a cryptographic operation.".
Could this be the reason why the validation is always false? If so, how to ensure that the unprotect method is called? By looking at the objects in both situations the UserManager seems to be using the correct IDataProtector.
Any help is appreciated!
Upvotes: 2
Views: 2538
Reputation: 1101
After trying to figure out why the SignInManager.PasswordSignIn method always fail as well the issue was discovered to be caused by the User's SecurityStamp not being set when it should have been.
Hope this helps someone else.
Upvotes: 1
Reputation: 4668
I had same kind of issue as you mentioned in the example SO post. I have used it as follow. In this case I have used SimpleInjector
IOC to register Usermanager
per web request.
I think you only need to create new DataProtectorTokenProvider
if any of dataProtectionProvider
not exists. Further, include DataProtectorTokenProvider creation inside the UserManager
constructor will fix the issue.
private static void InitializeUserManager(ApplicationUserManager manager, IAppBuilder app)
{
manager.UserValidator = new UserValidator<AspNetApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
var dataProtectionProvider = app.GetDataProtectionProvider();
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<AspNetApplicationUser>(
dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = TimeSpan.FromHours(3)
};
}
}
I have added token life time, but you can remove it if not needed.
Upvotes: 1