jstuardo
jstuardo

Reputation: 4383

How to forbid a locked out user to login in .NET Core 6 Identity

I have a custom user store in my .NET Core 6 site. The corresponding user table in DB contains a field called Lockout.

When a user signs in, I need that field to be used to forbid the user to login if that field is true.

The Login action of the Account Controller has this code:

            var result = await _signInManager.PasswordSignInAsync(model.Login, model.Password, model.RememberMe, lockoutOnFailure: false);
            if (result.Succeeded)
            {
                var appUser = await _userManager.FindByNameAsync(model.Login);
                if (appUser.Roles == null || appUser.Roles.Count == 0)
                {
                    await PerformLogOff();
                    return Json("ERROR: No tiene autorización para ingresar al sistema.");
                }
                else
                {
                    returnUrl ??= Request.Path.ToString();
                    appUser.LastLoggedOn = DateTime.Now;
                    if (!appUser.FirstLoggedOn.HasValue)
                        appUser.FirstLoggedOn = appUser.LastLoggedOn;
                    await _userManager.UpdateAsync(appUser);
                    return Json(returnUrl);
                }
            }
            else if (result.IsLockedOut)
                return Json("ERROR: Su usuario está bloqueado.");
            else if (result.RequiresTwoFactor)
                return Json("ERROR: El usuario requiere autenticación de doble factor.");
            else
                return Json("ERROR: Nombre de usuario o contraseña incorrectos.");

When a user is locked out, I need PasswordSignInAsync to return result.IsLockedOut.

How can I do it? Should I create a custom SignInManager? Notice that this has nothing to do with locking a user out when he fails to enter the password several times in login screen.

Thanks Jaime

Upvotes: 1

Views: 1064

Answers (4)

MD Zand
MD Zand

Reputation: 2605

You can do it yourself by adding another field (e.g. IsActive) to your ApplicationUser class.

Then you can check it before the line _signInManager.PasswordSignInAsync with some code like this:

var user = await _userManager.Users.Where(s => s.UserName == model.Username).FirstOrDefaultAsync();
if (user != null)
{
    if (!user.IsActive)
    {    
        ModelState.AddModelError("", "User has been deactivated"); //Log the failure                       
        return View();
    }
}

Upvotes: 3

Arun Kumar A.J
Arun Kumar A.J

Reputation: 171

To lockout a user, you have to call the SetLockoutEndDateAsync method in UserManager<> and pass in a future date. The following code locks out a user till the year 3000.

await userManager.SetLockoutEndDateAsync(userToRemove, new DateTimeOffset(3000, 1, 1, 1, 1, 1, TimeSpan.Zero));

Upvotes: 0

jgasiorowski
jgasiorowski

Reputation: 1033

If you really want result.IsLockedOut to be true then I can see two options:

  1. You would need to extend UserManager or AspNetUserManager and override virtual method IsLockedOutAsync (code) so it will be returning false based on your Lockout property.
  2. I don't know internals of your store but you could also manually set original LockoutEnd field in database to date from the next century e.g.:3000-01-01 when your application logic wants to set it to true or clear the field to unlock the user

For both options you need to watch out - SignInManager will only attempt to check lockouts if feature is enabled (code).

To enable it you should make sure that your custom UserStore implements IUserLockoutStore<TUser> interface. Simple override of SupportsUserLockout (code) virtual property to return always true in your custom UserManager might not work - because there are few other operations made on UserStore to count failures etc.

Probably you are not looking for opinion but I feel that your idea from comment to load user in Login action and check your custom property is the cleanest. It is not only about hacking your solution here - but it is usualy very harmful in development teams to change well documented, battle-tested frameworks like Identity to behave in a different way.

Upvotes: 0

Jason Pan
Jason Pan

Reputation: 21883

I have read source code about SignInManager.cs. And it should support forbid lockout user.

For more details, check the blog below, it contains the code useful to you.

User Lockout with ASP.NET Core Identity

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IactionResult> Login(UserLoginModel userModel, string returnUrl = null)
{
    if (!ModelState.IsValid)
    {
        return View(userModel);
    }

    var result = await _signInManager.PasswordSignInAsync(userModel.Email, userModel.Password, userModel.RememberMe, lockoutOnFailure: true);
    if (result.Succeeded)
    {
        return RedirectToLocal(returnUrl);
    }

    if (result.IsLockedOut)
    {
        var forgotPassLink = Url.Action(nameof(ForgotPassword),"Account", new { }, Request.Scheme);
        var content = string.Format("Your account is locked out, to reset your password, please click this link: {0}", forgotPassLink);

        var message = new Message(new string[] { userModel.Email }, "Locked out account information", content, null);
        await _emailSender.SendEmailAsync(message);

        ModelState.AddModelError("", "The account is locked out");
        return View();
    }
    else
    {
        ModelState.AddModelError("", "Invalid Login Attempt");
        return View();
    }
}

Upvotes: 0

Related Questions