How to ask for password when user open settings page in ASP.NET Core Identity?

Daniel

I configured Web app in ASP.NET Core 3.1 to use ASP.NET Core Identity with local accounts. Everything works correctly. I want to force user to insert password (or use external provider) again when he go to specific page e.g. account settings. I cannot find example with this case. Does somebody has experience with this? Maybe there is some article which I missed.

So far the only idea is to log out user and open login page but it's not logic because he should be able to open other pages without restrictions.

Chris

I've not tried this but I just stumbled across this blog post which sounds like it would work for you, I've kinda shortened it down to an almost TL;DR post

Setup the required password verification page

The RequirePasswordVerificationModel class implements the Razor page which requires that a user has verified a password for the identity user within the last ten minutes.

public class RequirePasswordVerificationModel : PasswordVerificationBase
{
    public RequirePasswordVerificationModel(UserManager<ApplicationUser> userManager) : base(userManager)
    {
    }

    public async Task<IActionResult> OnGetAsync()
    {
        var passwordVerificationOk = await ValidatePasswordVerification();
     
        if (!passwordVerificationOk)
        {
            return RedirectToPage("/PasswordVerification", 
                new { ReturnUrl = "/DoUserChecks/RequirePasswordVerification" });
        }

        return Page();
    }
}

The PasswordVerificationBase Razor page implements the PageModel. The ValidatePasswordVerification method checks if the user is already authenticated

public class PasswordVerificationBase : PageModel
{
    public static string PasswordCheckedClaimType = "passwordChecked";

    private readonly UserManager<ApplicationUser> _userManager;

    public PasswordVerificationBase(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    public async Task<bool> ValidatePasswordVerification()
    {
        if (User.Identity.IsAuthenticated)
        {
            if (User.HasClaim(c => c.Type == PasswordCheckedClaimType))
            {
                var user = await _userManager.FindByEmailAsync(User.Identity.Name);

                var lastLogin = DateTime.FromFileTimeUtc(
                    Convert.ToInt64(user.LastLogin));

                var lastPasswordVerificationClaim 
                    = User.FindFirst(PasswordCheckedClaimType);
                    
                var lastPasswordVerification = DateTime.FromFileTimeUtc(
                    Convert.ToInt64(lastPasswordVerificationClaim.Value));

                if (lastLogin > lastPasswordVerification)
                {
                    return false;
                }
                else if (DateTime.UtcNow.AddMinutes(-10.0) > lastPasswordVerification)
                {
                    return false;
                }

                return true;
            }
        }

        return false;
    }
}

If the user needs to re-enter credentials, the PasswordVerificationModel Razor page is used for this. This class was built using the identity scaffolded login Razor page from ASP.NET Core Identity. The old password verifications claims are removed using the UserManager service. A new password verification claim is created, if the user successfully re-entered the password and the sign in is refreshed with the new ClaimIdentity instance.

public class PasswordVerificationModel : PageModel
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly ILogger<PasswordVerificationModel> _logger;

    public PasswordVerificationModel(SignInManager<ApplicationUser> signInManager,
        ILogger<PasswordVerificationModel> logger,
        UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _logger = logger;
    }

    [BindProperty]
    public CheckModel Input { get; set; }

    public IList<AuthenticationScheme> ExternalLogins { get; set; }

    public string ReturnUrl { get; set; }

    [TempData]
    public string ErrorMessage { get; set; }

    public class CheckModel
    {
        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }

    public async Task<IActionResult> OnGetAsync(string returnUrl = null)
    {
        if (!string.IsNullOrEmpty(ErrorMessage))
        {
            ModelState.AddModelError(string.Empty, ErrorMessage);
        }

        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
        }

        var hasPassword = await _userManager.HasPasswordAsync(user);
        if (!hasPassword)
        {
            return NotFound($"User has no password'{_userManager.GetUserId(User)}'.");
        }

        returnUrl ??= Url.Content("~/");
        ReturnUrl = returnUrl;

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl ??= Url.Content("~/");

        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
        }

        if (ModelState.IsValid)
        {
            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, set lockoutOnFailure: true
            var result = await _signInManager.PasswordSignInAsync(user.Email, Input.Password, false, lockoutOnFailure: false);
            if (result.Succeeded)
            {
                _logger.LogInformation("User password re-entered");

                await RemovePasswordCheck(user);
                var claim = new Claim(PasswordVerificationBase.PasswordCheckedClaimType,
                    DateTime.UtcNow.ToFileTimeUtc().ToString());
                await _userManager.AddClaimAsync(user, claim);
                await _signInManager.RefreshSignInAsync(user);

                return LocalRedirect(returnUrl);
            }
            if (result.IsLockedOut)
            {
                _logger.LogWarning("User account locked out.");
                return RedirectToPage("./Lockout");
            }
            else
            {
                ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                return Page();
            }
        }

        // If we got this far, something failed, redisplay form
        return Page();
    }

    private async Task RemovePasswordCheck(ApplicationUser user)
    {
        if (User.HasClaim(c => c.Type == PasswordVerificationBase.PasswordCheckedClaimType))
        {
            var claims = User.FindAll(PasswordVerificationBase.PasswordCheckedClaimType);
            foreach (Claim c in claims)
            {
                await _userManager.RemoveClaimAsync(user, c);
            }
        }
    }
}

The PasswordVerificationModel Razor page html template displays the user input form with the password field.

@page
@model PasswordVerificationModel

@{
    ViewData["Title"] = "Password Verification";
}

<h1>@ViewData["Title"]</h1>
<div class="row">
    <div class="col-md-4">
        <section>
            <form id="account" method="post">
                <h4>Verify account using your password</h4>
                <hr />
                <div asp-validation-summary="All" class="text-danger"></div>
                <div class="form-group">
                    <label asp-for="Input.Password"></label>
                    <input asp-for="Input.Password" class="form-control" />
                    <span asp-validation-for="Input.Password"
                    class="text-danger"></span>
                </div>
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">
                    Re-enter password
                    </button>
                </div>
            </form>
        </section>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

The Login Razor page needs to be updated to add a login file time value for DateTime.UtcNow when the login successfully occurred. This value is used in the base Razor page to verify the password check. The LastLogin property was added for this.

var result = await _signInManager.PasswordSignInAsync(
    Input.Email, Input.Password, 
    Input.RememberMe, 
    lockoutOnFailure: false);
    
if (result.Succeeded)
{
    _logger.LogInformation("User logged in.");

    var user = await _userManager.FindByEmailAsync(Input.Email);
    if (user == null)
    {
        return NotFound("help....");
    }
    user.LastLogin = DateTime.UtcNow.ToFileTimeUtc().ToString();

    var lastLoginResult = await _userManager.UpdateAsync(user);

    return LocalRedirect(returnUrl);
}

The LastLogin property was added to the ApplicationUser which implements the IdentityUser. This value is persisted to the Entity Framework Core database.

public class ApplicationUser : IdentityUser
{
    public string LastLogin { get; set; }
}

When the application is started, the user can login and will need to verify a password to access the Razor page implemented to require this feature.

demo of requiring additional password

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How override ASP.NET Core Identity's password policy

InvalidOperationException when registering a new user with ASP .NET Core Identity and EntityFrameworkCore

ASP>NET Identity: How to compare a password entered by the user with a hashed password?

How to sign out other user in ASP.NET Core Identity

How to add ASP.Net identity to Asp.Net Core when webApi template is selected?

How to edit a user in ASP.NET Identity

ASP.Net Core how to get the user role in EF Core and Identity

Regex for default ASP.NET Core Identity Password

In a default ASP.NET Core project, why does the "Forgot password" functionality ask for user email twice?

How do I seed user roles in Identity ASP.NET Core 2.1 (Identity Scaffolded)

How to get Asp.net Core Identity User in View

ASP.NET Core Identity, IdentityServer4 and Changing a password

How to protect Asp.net core 2.1 and Vue.js single page application with Identity Server 4

Store User Settings in ASP.NET Core Identity AspNetUsers Table or Not

How to manually hash a password using Asp.Net Core 2.2 / IdentityServer4 / SP.NET Core Identity

Why sign-in a new user when using ASP.NET Core Identity and token-based auth?

Show the user a message when you enter the password incorrectly ASP.NET Core 3.0 MVC

LDAP-Error in Identity Server 4.6 when using user registration with "Ask password from user"

ASP.NET Core Identity Settings Not Working

Showing User Names (stored in ASP.Net Core Identity system), instead of IDs When loading a table

ASP.Net Core API: How to link User Identity to a Contact

How set a identity scaffolding item/page how initial page in asp.net MVC core?

ASP.NET Core how to redirect user to a page depending on a boolean

When using ASP.NET core Identity, it is better to extend the User class or write separate entities that relate to it

Allow only numeric password in ASP.NET Core identity

ASP.NET Core Identity user groups

What are ai_user and ai_session cookies in ASP NET Core Identity and how to configure it?

How can I have multiple Identity user types when using ASP.NET Core Identity and Entity Framework Core?

How to check if user is logged in to ASP.NET Core web application when using ASP.NET Core Web API to house identity