Idenitity UserManager ConfirmEmailAsync not implementing IUserEmailStore - asp.net-mvc

MVC application using AspNet.Identity
I've set my application up so when someone registers an account they are sent a verification email with a token to confirm the account.
I have had this working previously in a different application so am not sure why this isn't working.
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
try
{
var provider = new DpapiDataProtectionProvider("SOMETHING");
UserManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("EmailConfirmation"));
result = await UserManager.ConfirmEmailAsync(userId, code);
}
Now when i breakpoint my code, it is giving me the error when i get to ConfirmEmailAsync, i am passing ina userId and Code, but for some reason it is wanting me to implement IUserEmailStore.
I haven't had to do this previously, and the above code has worked fine.

For anyone else having this issue. It was because my Microsoft.AspNet.Identity.EntityFramework was on version 1.
Its partly(read all) my fault as I assumed when I updated the identity it would update all of the identity references, apparently not.

Related

ASP.NET Core Identity x Docker - Confirmation link invalid on other instances

I am currently developing a web API with ASP.NET Core, using Microsoft Identity Core as for the identity management. When a user registers, it is sent an email with a confirmation link - pretty basic so far.
The problem comes when publishing my API to Azure using a containerized Azure App Service, and when setting the number of instances to 2 or more. The confirmation link seems to be working only half the time; tests on my dev machine with multiple Docker containers running seemed to confirm that fact, as the confirmation link could be validated only on the instance the user had registered on (hence the instance where the confirmation link was created).
Having dug a bit on the subject by reading this article by Steve Gordon, and explored the public GitHub code for Identity Core, I still don't understand why different container instances would return different results when validating the token, as the validation should mainly be based on the user SecurityStamp (that remains unchanged between the instances becauses they all link to the same database).
Also, enabling 'debug' logging for the Microsoft.AspNetCore.Identity only logged
ValidateAsync failed: unhandled exception was thrown.
during token validation from the DataProtectorTokenProvider.ValidateAsync() method from AspNetCore.Identity, so it is not very helpful as I can't see precisely where the error happens...
May this be linked to the token DataProtector not being the same on different instances? Am I searching in the wrong direction? Any guess, solution or track for this?
Help would be immensely appreciated 🙏
Here is some simplified code context from my app for the record.
UserManager<User> _manager; // Set from DI
// ...
// Creating the user and sending the email confirmation link
[HttpGet(ApiRoutes.Users.Create)]
public async Task<IActionResult> RegisterUser(UserForRegistrationDto userDto)
{
var user = userDto.ToUser();
await _manager.CreateAsync(user, userDto.Password);
// Create the confirmation token
var token = await _manager.CreateEmailConfirmationTokenAsync(user);
// Generate the confirmation link pointing to the below 'ConfirmEmail' endpoint
var confirmationLink = Url.Action("ConfirmEmail", "Users",
new { user.Email, token }, Request.Scheme);
await SendConfirmationEmailAsync(user, confirmationLink); // Some email logic elsewhere
return Ok();
}
// Confirms the email using the passed token
[HttpGet(ApiRoutes.Users.ValidateEmail)]
public async Task<IActionResult> ConfirmEmail(string email, string token)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return NotFound();
}
var result = await _userManager.ConfirmEmailAsync(user, token);
if (!result.Succeeded)
{
return BadRequest();
}
return Ok();
}
Token generated based on security stamp but Identity uses DataProtector to protect the token content. By default the data protection keys stored at location %LOCALAPPDATA%\ASP.NET\
If the application runs on single machine it is perfectly fine as there is no scope for key mismatch. But deployed on multiple instances the tokens will not work sometimes as the Keys are different on different machines and there is no guarantee the generation of token and validation of token will come to same instance.
To solve user redis or azurekeyvault
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-6.0#persisting-keys-with-redis

Sustainsys SAML2 Sample for ASP.NET Core WebAPI without Identity

Does anyone have a working sample for Sustainsys Saml2 library for ASP.NET Core WebAPI only project (no Mvc) and what's more important without ASP Identity? The sample provided on github strongly relies on MVC and SignInManager which I do not need nor want to use.
I added Saml2 authentication and at first it worked fine with my IdP (I also checked the StubIdP provided by Sustainsys) for first few steps so:
IdP metadata get properly loaded
My API properly redirects to sign-in page
Sign-in page redirects to /Saml2/Acs page, and I see in the logs that it parses the result successfully
However I don't know how to move forward from there and extract user login and additional claims (my IdP provided also an e-mail, and it is included in SAML response which I confirmed in the logs).
Following some samples found on the web and modyfing a little bit the MVC Sample from GitHub I did the following:
In Startup.cs:
...
.AddSaml2(Saml2Defaults.Scheme,
options =>
{
options.SPOptions.EntityId = new EntityId("...");
options.SPOptions.ServiceCertificates.Add(...));
options.SPOptions.Logger = new SerilogSaml2Adapter();
options.SPOptions.ReturnUrl = new Uri(Culture.Invariant($"https://localhost:44364/Account/Callback?returnUrl=%2F"));
var idp =
new IdentityProvider(new EntityId("..."), options.SPOptions)
{
LoadMetadata = true,
AllowUnsolicitedAuthnResponse = true, // At first /Saml2/Acs page throwed an exception that response was unsolicited so I set it to true
MetadataLocation = "...",
SingleSignOnServiceUrl = new Uri("...") // I need to set it explicitly because my IdP returns different url in the metadata
};
options.IdentityProviders.Add(idp);
});
In AccountContoller.cs (I tried to follow a somewhat similar situation described at how to implement google login in .net core without an entityframework provider):
[Route("[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
private readonly ILog _log;
public AccountController(ILog log)
{
_log = log;
}
[HttpGet("Login")]
[AllowAnonymous]
public IActionResult Login(string returnUrl)
{
return new ChallengeResult(
Saml2Defaults.Scheme,
new AuthenticationProperties
{
// It looks like this parameter is ignored, so I set ReturnUrl in Startup.cs
RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
});
}
[HttpGet("Callback")]
[AllowAnonymous]
public async Task<IActionResult> LoginCallback(string returnUrl)
{
var authenticateResult = await HttpContext.AuthenticateAsync(Constants.Auth.Schema.External);
_log.Information("Authenticate result: {#authenticateResult}", authenticateResult);
// I get false here and no information on claims etc.
if (!authenticateResult.Succeeded)
{
return Unauthorized();
}
// HttpContext.User does not contain any data either
// code below is not executed
var claimsIdentity = new ClaimsIdentity(Constants.Auth.Schema.Application);
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
_log.Information("Logged in user with following claims: {#Claims}", authenticateResult.Principal.Claims);
await HttpContext.SignInAsync(Constants.Auth.Schema.Application, new ClaimsPrincipal(claimsIdentity));
return LocalRedirect(returnUrl);
}
TLDR: Configuration for SAML in my ASP.NET Core WebApi project looks fine, and I get success response with proper claims which I checked in the logs. I do not know how to extract this data (either return url is wrong or my callback method should work differently). Also, it is puzzling why successfuly redirect from SSO Sign-In page is treated as "unsolicited", maybe this is the problem?
Thanks for any assistance
For anyone who still needs assistance on this issue, I pushed a full working example to github which uses a .Net Core WebAPI for backend and an Angular client using the WebAPI. you can find the example from here:
https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample
As it turned out, the various errors I've been getting were due to my solution being hosted inside docker container. This caused a little malfunction in internal aspnet keychain. More details can be found here (docker is mentioned almost at the end of the article):
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?tabs=aspnetcore2x&view=aspnetcore-2.2
Long story short, for the code to be working I had to add only these lines:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("/some/volume/outside/docker")); // it needs to be outside container, even better if it's in redis or other common resource
It fixed everything, which includes:
Sign-in action to external cookie
Unsolicited SSO calls
Exceptions with data protection key chain
So it was very difficult to find, since exceptions thrown by the code didn't point out what's going on (and the unsolicited SSO calls made me think that the SSO provider was wrongly configured). It was only when I disassembled the Saml2 package and tried various code pieces one by one I finally encoutered proper exception (about the key chain) which in turned led me to an article about aspnet data protection.
I provide this answer so that maybe it will help someone, and I added docker tag for proper audience.

UserManager VerifyUserTokenAsync Always False

I'm generating a usertoken like so
public async Task GenerateCode()
{
var code = await UserManager.GenerateUserTokenAsync("heymega", new Guid("16139fcd-7ae0-449c-ad1c-f568bbe46744"));
}
I then pass the same token into another action via a separate request
public async Task ValidateCode(string code)
{
var valid = await UserManager.VerifyUserTokenAsync(new Guid("16139fcd-7ae0-449c-ad1c-f568bbe46744"), "heymega", code); //Returns False
}
However, the response from the VerifyUserTokenAsync method is always false.
If I were to generate the code and verify within the same action
public async Task GenerateCode()
{
var code = await UserManager.GenerateUserTokenAsync("heymega", new Guid("16139fcd-7ae0-449c-ad1c-f568bbe46744"));
var valid = await UserManager.VerifyUserTokenAsync(new Guid("16139fcd-7ae0-449c-ad1c-f568bbe46744"), "heymega", code); //Returns True
}
It returns true.
Why can't the Verify method verify the code in a separate request? Am I missing something obvious?
I finally figured this after pulling my hair out for hours. You need to URL encode the code and I decided to use the HttpUtility class for this.
HttpUtility.UrlEncode(code);
When it comes to verifying the code, you do not need to URL decode the code.
Having just burned 2 days on this issue, here is another reason this might be happening to you.
In your Startup.cs - ConfigureServices(IServiceCollection services) method, ensure that:
services.AddAuthentication
Appears BEFORE
services.AddIdentity
Otherwise calls to VerifyUserTokenAsync will always return false
Cannot solve this problem until haven't used this:
UserManager.VerifyUserTokenAsync(userId, AccountLockedOutPurpose, code).WithCurrentCulture<bool>();
.WithCurrentCulture() - used in all methods such as ResetPasswordAsync etc.)
In my situation I was instantiating a UserManager on demand when one was needed, as opposed to generating one per Owin context in my startup pipeline. Behavior wise, if I validated the token with the same instance of UserManager that created it, it would return true. But if I did an actual forgot password flow where the validation is in a separate request, it was always false.
Switching my setup so that a UserManager was created per owin context resolved the issue for me. Apparently there is some dependency on Owin when it comes to validating tokens.
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
Not sure if OP is using .Net Core or not, but if someone comes across this and you're using dependency injection, the solution for me was to scope the UserManager as a singleton.
services.AddSingleton<UserManager<YourUserAccountModel>>();
I believe this is because when the user clicks the confirm email link in their inbox, a new UserManager instance is injected to the controller and does not have the same key that was used to generate the token to begin with. Therefore it cannot verify the token.
For me, I got the same issue. and the solution was very simple.
In my case, I add the purpose with white space like this "Email Confirmation".
the problem was solved when I removed any white space "EmailConfirmation".
bool IsTokenValed = await userManager.VerifyUserTokenAsync(user, userManager.Options.Tokens.EmailConfirmationTokenProvider, "EmailConfirmation", token);

Change e-mail or password in AspNetUsers tables in ASP.NET Identity 2.0

I have implemented the ForgotPassword (with token reset) into my MVC 5 application. We are in production. Although this works in majority of the cases, many of our end-users are of older age and get confused when they cannot login and need a reset. So in those situations, I am considering giving one of our admin staff the ability to reset a user's password and giving them the new password on the phone. The data is not that sensitive.
I tried this:
public ActionResult ResetPassword()
{ UserManager<IdentityUser> userManager =
new UserManager<IdentityUser>(new UserStore<IdentityUser>());
var user = userManager.FindByEmail("useremail.samplecom");
userManager.RemovePassword(user.Id);
userManager.AddPassword(user.Id, "newpassword");
}
I get a cryptic error stating Invalid Column EMail, Invalid Column Email Confirmed ......
I also tried the userManager.ResetPassword(), but abandoned that idea because it needs a token reset. I want to bypass it.
What am I not seeing?
Thanks in advance.
I also tried the userManager.ResetPassword(), but abandoned that idea because it needs a token reset. I want to bypass it.
How about you just generate the token and pass it to the Reset routine ?
var userManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
var code = await userManager.GeneratePasswordResetTokenAsync("username");
var result = await userManager.ResetPasswordAsync("username", code, "your new password");
if (!result.Succeeded)
{
//password does not meet standards
}
The idea here is you are just emulating/bypassing the usual routine of sending the token to the client (via email) and having the link that they click on call ResetPasswordAsync
I'm not completely sure if this will work in your implementation but I use the following code with success in a use case which has basically the same requirements as yours. The difference is that I'm not letting any user reset it's own password. This is always the task of an admin.
I'm bypassing the ApplicationUserManager and edit the information directly in the table, using just Entity Framework.
// I created an extension method to load the user from the context
// you will load it differently, but just for completeness
var user = db.LoadUser(id);
// some implementation of random password generator
var password = General.Hashing.GenerateRandomPassword();
var passwordHasher = new Microsoft.AspNet.Identity.PasswordHasher();
user.PasswordHash = passwordHasher.HashPassword(password);
db.SaveChanges();
You have to get the user from the database and generate the code not by username :
public async Task<Unit> ResetPassword(string userName, string password)
{
if (!string.IsNullOrWhiteSpace(userName))
{
var returnUser = await _userManager.Users.Where(x => x.UserName == userName).FirstOrDefaultAsync();
var code = await _userManager.GeneratePasswordResetTokenAsync(returnUser);
if (returnUser != null)
await _userManager.ResetPasswordAsync(returnUser, code, password);
}
return Unit.Value;
}

Manually validating a password reset token in ASP.NET Identity

I would like to manually validate a password reset token in ASP.NET Identity 2.0. I'm trying to create my own version of UserManager.ResetPasswordAsync(string userId, string token, string newPassword) that takes and IdentityUser instead of userId like this:
UserManager.ResetPasswordAsync(IdentityUser user, string token, string newPassword)
Not sure if I am doing this right, but here I am attempting to validate the code that was emailed to the user in an earlier step. I have not modified the code/token that sends the email to the user and generates the code. I am assuming this is the correct method to call, but the purpose argument is incorrect. (I tried passing "ASP.NET Identity" but no dice.)
if (await userManager.UserTokenProvider.ValidateAsync(purpose: "?", token: code, manager: userManager, user: user))
{
return IdentityResult.Success;
}
else
{
return new IdentityResult("Invalid code.");
}
If someone could fill me in on the details of how it works out of the box, or point me at Microsoft's source code for UserManager.ResetPasswordAsync(IdentityUser user, string token, string newPassword) that would be most appreciated!
I overcame my problem by setting the purpose to "ResetPassword".
Below is a snippet of the final result in case someone wants to do something similar. It is a method in my ApplicationUserManager class. Realize, though, that some of the exception handling that Microsoft implements is missing or not localized because certain private variables, methods, and resources used in their code are inaccessible. It's unfortunate they did not make that stuff protected so that I could have gotten at it. The missing ThrowIfDisposed method call in particular is interesting (and bazaar) to me. Apparently they are anticipating method calls after an instance has been disposed in order to provide a friendlier error message and avoid the unexpected.
public async Task<IdentityResult> ResetPasswordAsync(IdentityUser user,
string token, string newPassword)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
// Make sure the token is valid and the stamp matches.
if (!await UserTokenProvider.ValidateAsync("ResetPassword", token,
this, user))
{
return IdentityResult.Failed("Invalid token.");
}
// Make sure the new password is valid.
var result = await PasswordValidator.ValidateAsync(newPassword)
.ConfigureAwait(false);
if (!result.Succeeded)
{
return result;
}
// Update the password hash and invalidate the current security stamp.
user.PasswordHash = PasswordHasher.HashPassword(newPassword);
user.SecurityStamp = Guid.NewGuid().ToString();
// Save the user and return the outcome.
return await UpdateAsync(user).ConfigureAwait(false);
}
It appears that the code for Microsoft.AspNet.Identity has not been Open Sourced according to the Codeplex repository located at:
https://aspnetidentity.codeplex.com/SourceControl/latest#Readme.markdown
At present, the ASP.NET Identity framework code is not public and
therefore will not be published on this site. However, we are planning
to change that, and as soon as we are able, the code will be published
in this repository.
However I did find this which might be the source for the UserManager based on the debug symbols:
UserManager Source Code
I also found these posts which might help:
Implementing custom password policy using ASP.NET Identity
UserManager Class Documentation
IUserTokenProvider Interface Documentation

Resources