Reputation: 11
I am configuring my application to handle the following scenario: when I create a new user as an admin, a temporary (random) password is generated for that user. After the user logs in for the first time, they are required to change their password. This requirement is tracked using the MustChangePassword
field in the AppUser : Identity
class.
I want to enforce that users must change their password immediately after logging in. If the user tries to access any other links, they should be redirected to the Change Password page. I thought configuring middleware would be the best approach for this.
However, with my current configuration (see below), even when I submit the change password form, it still redirects to /Account/ChangePassword.
public class CheckPasswordChangeMiddleware
{
private readonly RequestDelegate _next;
public CheckPasswordChangeMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, UserManager<AppUser> userManager, SignInManager<AppUser> signInManager)
{
if (context.User.Identity.IsAuthenticated)
{
var user = await userManager.GetUserAsync(context.User);
if (user != null && user.MustChangePassword && !context.Request.Path.StartsWithSegments("/Account/ChangePassword"))
{
context.Response.Redirect("/Account/ChangePassword");
return;
}
}
await _next(context);
}
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangePassword(ChangePassword model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await _accountService.ChangePasswordAsync(model);
if (result)
{
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError(string.Empty, "An error occurred while changing the password.");
return View(model);
}
public async Task<bool> ChangePasswordAsync(ChangePassword model)
{
var user = await _userManager.GetUserAsync(_httpContextAccessor.HttpContext!.User);
if (user == null)
{
return false;
}
var changePasswordResult = await _userManager.ChangePasswordAsync(user, model.CurrentPassword, model.NewPassword);
if (changePasswordResult.Succeeded)
{
user.MustChangePassword = false;
await _userManager.UpdateAsync(user);
await _signInManager.RefreshSignInAsync(user);
return true;
}
return false;
}
Thank you for any help or suggestions!
Upvotes: 1
Views: 425
Reputation: 4267
The best way would be to not add that MustChangePassword
field in your AppUser
there's no need for it. You will only ever use it once and it will remain in there unused.
Instead, add a claim
against that user with type new Claim("MUST_CHANGE_PASSWORD","")
.
In your middleware:
public async Task InvokeAsync (HttpContext context)
{
if (context.User.Identity.IsAuthenticated)
{
if (context.User.HasClaim("MUST_CHANGE_PASSWORD","") && context.Request.Path != "/Account/ChangePassword")
{
context.Response.Redirect("/Account/ChangePassword");
return;
}
}
await _next(context);
}
In your ChangePassword
HttpPost
method remove that claim userManager.RemoveClaimAsync(User, new Claim("MUST_CHANGE_PASSWORD",""))
once password is changed.
Upvotes: 1