添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Ditch JavaScript and learn BLAZOR WEBASSEMBLY 🔥 Utlilize the power of scalability with MICROSERVICES IN .NET! 🔥 Learn how to create fast web apps with MINIMAL APIs IN ASP.NET CORE! 🔥
  • ASP.NET Core Web API Best Practices
  • Top REST API Best Practices
  • 10 Things You Should Avoid in Your ASP.NET Core Controllers
  • C# Back to Basics
  • C# Intermediate
  • Design Patterns in C#
  • Sorting Algorithms in C#
  • Docker Series
  • HTTP Series
  • About Us
  • Archives
  • Our Editors
  • Leave Us a Review
  • Code Maze Reviews
  • Contact Us
  • Ready to take your skills to the next level? Jump into our high-impact courses in web development and software architecture, all with a focus on mastering the .NET/C# framework. Whether you're building sleek web applications or designing scalable software solutions , our expert-led training will give you the tools to succeed. Visit our COURSES page now and kickstart your journey!

    The user lockout feature is a way to improve application security by locking out a user who enters a password incorrectly several times. This technique can help us in protecting against brute force attacks, where an attacker repeatedly tries to guess a password.

    In this article, we are going to learn how to implement the user lockout functionality in our application and how to implement a custom password validator that extends default password policies.

    To download the source code for the video, visit our Patreon page (YouTube Patron tier).

    To navigate through the entire series, visit the ASP.NET Core Identity series page.

    Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
    Become a patron at Patreon!

    User Lockout Configuration

    The default configuration for the lockout functionality is already in place, but if we want, we can apply our configuration. To do that, we have to modify the AddIndentity method in the ConfigureService method for .NET 5 or previous versions:

    services.AddIdentity<User, IdentityRole>(opt =>
        //previous code removed for clarity reasons
        opt.Lockout.AllowedForNewUsers = true;
        opt.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(2);
        opt.Lockout.MaxFailedAccessAttempts = 3;
    

    In .NET 6, we have to modify the Program class:

    builder.Services.AddIdentity<User, IdentityRole>(opt =>
        //previous code removed for clarity reasons
        opt.Lockout.AllowedForNewUsers = true;
        opt.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(2);
        opt.Lockout.MaxFailedAccessAttempts = 3;
    

    The user lockout feature is enabled by default, but we state that here explicitly by setting the AllowedForNewUsers property to true. Additionally, we configure a lockout time span to two minutes (default is five) and maximum failed login attempts to three (default is five). Of course, the time span is set to two minutes just for the sake of this example, that value should be a bit higher in production environments.

    So, this is the way to configure the user lockout functionality in our application by using IdentityOptions.

    Implementing User Lockout in the Login Action

    If we check the Login action, we are going to see this code:

    var result = await _signInManager.PasswordSignInAsync(userModel.Email, 
        userModel.Password, userModel.RememberMe, false);

    The last parameter from the PasswordSignInAsync method stands for enabling or disabling the lockout feature. For now, it’s disabled and we have to enable it by setting it to true. Furthermore, this method returns a SignInResult with the IsLockedOut property we can use to check whether the account is locked out or not.

    With that said, let’s modify the Login action:

    [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();
            ModelState.AddModelError("", "Invalid Login Attempt");
            return View();
    

    By setting the lockoutOnFailure parameter to true, we enable the lockout functionality, thus enable  modification of the AccessFailedCount and LockoutEnd columns in the AspNetUsers table:

    The AccessFailedCount column will increase for every failed login attempt and reset once the account is locked out. Additionally, the LockoutEnd column will have a DateTime value to represent the period until this account is locked out.

    As we can see in the code, we check the IsLockedOut property, and if it is true, we send an email with the forgot password link and appropriate message, and return information about the locked out account, to the user.

    About an Email Message

    Sending an email message to inform a user about a locked-out account is a good practice. By doing that, we encourage the user to act proactively. That user can reset the password, or report that something is strange because they didn’t try to log in, which means that someone is trying to hack the account, etc.

    Testing Time

    Okay, let’s test this implementation.

    If we try to log in with the wrong credentials, we will get the Invalid Login Attempt error and the AccessFailedCount column will increase:

    Now, if we try the same credentials two more times:

    We can see the account locked out and we can confirm that in the database:

    Also, we can see the AccessFailedCount is reset.

    You can check your email, to find the link to the forgot password action. From there, everything is familiar because we talked about it in a previous article.

    Custom Password Validation

    As we can see from the previous example, the user gets an email with the link to reset the password. But, even though IdentityOptions already has different password configuration properties, we can add custom validations as well. For example, we don’t want a password to be the same as a username or we don’t want a password to contain the word password in it, etc.

    You get the point.

    Well, let’s see how to do that.

    First, we are going to create a new folder CustomValdiators with a single class inside:

    public class CustomPasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class
        public async Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user, string password)
            var username = await manager.GetUserNameAsync(user);
            if (username.ToLower().Equals(password.ToLower()))
                return IdentityResult.Failed(new IdentityError { Description = "Username and Password can't be the same.", Code = "SameUserPass" });
            if (password.ToLower().Contains("password"))
                return IdentityResult.Failed(new IdentityError { Description = "The word password is not allowed for the Password.", Code = "PasswordContainsPassword" });
            return IdentityResult.Success;
    

    We have to inherit from the IPasswordValidator<TUser> interface and implement the ValidateAsync method. Inside, we extract a username from a current user and then execute the required validations. If these validations check out, we return the Failed identity result, otherwise, we return success.

    Now, we have to register this custom validator:

    services.AddIdentity<User, IdentityRole>(opt =>
        //code removed for clarity reasons
     .AddEntityFrameworkStores<ApplicationContext>()
     .AddDefaultTokenProviders()
     .AddDefaultTokenProviders()                
     .AddTokenProvider<EmailConfirmationTokenProvider<User>>("emailconfirmation")
     .AddPasswordValidator<CustomPasswordValidator<User>>();
    

    And that’s it. With the help of the AddPasswordValidator method, we can register our custom validator class. So, the final step is to test this feature.

    If we try to use the password with the „password“ word in it:

    And if we try to use the same username and password:

    Excellent. This also works for the Registration process.

    Conclusion

    In this article, we’ve learned:

  • How to create a custom Lockout configuration
  • The way to implement User Lockout functionality
  • Why is a good practice to send an email message for lockout
  • How to implement custom password validation
  • In the next article, we are going to talk about two-way authentication in ASP.NET Core Identity.

    So, stay with us.

    Ready to take your skills to the next level? Jump into our high-impact courses in web development and software architecture, all with a focus on mastering the .NET/C# framework. Whether you're building sleek web applications or designing scalable software solutions, our expert-led training will give you the tools to succeed. Visit our COURSES page now and kickstart your journey!
    Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
    Become a patron at Patreon!