Back to: ASP.NET Core Identity Tutorials
How to Change Password in ASP.NET Core Identity
In this article, I will discuss How to Implement the Change Password Feature in ASP.NET Core Identity. Please read our previous article discussing How to Store Tokens in ASP.NET Core Identity.
What is a Change Password in Web Applications?
Change Password is a functionality provided in web applications that allows authenticated users to update their current password to a new one. It ensures that users can control their account’s security by modifying their password when necessary. It typically involves verifying the user’s current password and ensuring the new password meets specific security criteria before updating it in the system.
Why Do We Need to Implement Change Password?
Implementing a Change Password feature is crucial for the following reasons:
- Security: Passwords can be compromised over time, reused across different services, or become known to unauthorized parties. Allowing users to change their passwords helps maintain a secure environment.
- Compliance and Best Practices: Many organizations or regulations mandate periodic password changes. Providing an easy way for users to update their credentials helps satisfy these requirements.
- Improved User Control: Users should have control over their own security. Implementing a self-service password change interface reduces support overhead and improves user satisfaction.
- Recover from Security Incidents: Users can change passwords after a potential data breach or phishing attack. Even if users suspect (but aren’t sure) their accounts might be compromised, having a way to change the password easily helps them quickly secure their accounts without admin intervention.
How to Implement Change Password in ASP.NET Core Identity:
A logged-in user should have the ability to change their password securely. When the user is logged in, the “My Account” dropdown should appear in the UI. This dropdown should include options such as “Change Password” and “Sign Out” as shown in the below image.
Change Password Page:
Once the user clicks on the “Change Password” link, they should be redirected to a page where they can enter the following information:
- Current Password: To verify that the user is the legitimate account holder.
- New Password: The new password that the user wants to set.
- Confirm New Password: To ensure that the user typed the new password correctly.
After entering these details, the user can click on the “Update” button to submit the form, as shown in the below image.
Why Is the Old Password Required to Change the Password?
Requiring the current (old) password during a password change process is a standard security measure that serves several purposes:
- Verification of Identity: Asking for the old password ensures that the legitimate account owner is requesting to change the password. It’s a form of re-authentication to verify the user’s identity.
- Protection Against Unauthorized Changes: This prevents someone else from changing the password if the user accidentally leaves their account logged in on a shared or public device.
Password Update and Confirmation
Once the user submits the form with the correct details, the system will:
- Validate the Old Password: The current password will be checked.
- Update the Password: If the old password is correct, the new password will be updated.
- Confirmation Message: The user will be shown a success message indicating that their password has been updated.
We will display the following confirmation message:
Sending a Password Change Notification to User’s Email:
After a successful password change, a notification email should be sent to the user to inform them of the change. This email will contain important details for security purposes, such as:
- Time of Change: When the password changes (Date and Time).
- Location: Where the password change was made (e.g., the city, country).
- Device Information: The device used to change the password (e.g., browser and OS).
This ensures that the user is alerted if an unauthorized party changes the password. We will send the following notification email to the user.
How to Implement Change Password in ASP.NET Core Identity
Changing a user’s password in ASP.NET Core Identity typically involves the following steps:
- Get the Current User: Retrieve the currently logged-in user. This can usually be done via the UserManager class provided by ASP.NET Core Identity.
- Verify the Current Password: Before allowing a password change, first, we need to verify the user’s current password. This can be done using the UserManager.CheckPasswordAsync method.
- Change the Password: If the current password is verified, you can then change the password using the UserManager.ChangePasswordAsync method.
- Handle the Result: After attempting to change the password, you should check the result to see if it was successful and handle any errors.
- Send Password Change Notification: If the password is changed successfully, we need to inform the user by sending a Notification Email.
- Sign-in the User Again: After changing the password, it’s a good idea to re-sign the user in again with the new password details automatically.
Let us proceed and see how we can implement this step by step in our application.
Change Password View Model:
First, create a model for changing the password form. The model will hold the current password and new password and confirm new password properties. So, create a class file named ChangePasswordViewModel.cs within the Models folder and copy and paste the following code.
using System.ComponentModel.DataAnnotations; namespace ASPNETCoreIdentityDemo.Models { public class ChangePasswordViewModel { [Required(ErrorMessage = "Current Password is required.")] [DataType(DataType.Password, ErrorMessage = "Please enter a valid password.")] [Display(Name = "Current Password")] public string CurrentPassword { get; set; } [Required(ErrorMessage = "New Password is required.")] [DataType(DataType.Password, ErrorMessage = "Please enter a valid password.")] [Display(Name = "New Password")] [MinLength(8, ErrorMessage = "New Password must be at least 8 characters long.")] public string NewPassword { get; set; } [Required(ErrorMessage = "Confirm New Password is required.")] [DataType(DataType.Password, ErrorMessage = "Please enter a valid password.")] [Display(Name = "Confirm New Password")] [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] public string ConfirmPassword { get; set; } } }
ChangePassword Get Action Method
Please add the following ChangePassword Get Action Method within the Account Controller. This action method will display the change password form, which the user can use to change his/her password.
[Authorize] [HttpGet] public IActionResult ChangePassword() { return View(); }
ChangePassword View
Please add a view named ChangePassword.cshtml within the Views/Account directory and copy and paste the following code. The model for this view is ChangePasswordModel.
@model ChangePasswordViewModel @{ ViewData["Title"] = "Change Password"; } <div class="container mt-4"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card shadow"> <div class="card-header bg-primary text-white text-center"> <h4>Change Password</h4> </div> <div class="card-body"> <p class="text-muted text-center"> Update your password to keep your account secure. Ensure your new password meets security requirements. </p> <hr /> <form method="post" asp-action="ChangePassword" asp-controller="Account"> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group mb-3"> <label asp-for="CurrentPassword" class="form-label">Current Password</label> <input asp-for="CurrentPassword" class="form-control" placeholder="Enter your current password" /> <span asp-validation-for="CurrentPassword" class="text-danger"></span> </div> <div class="form-group mb-3"> <label asp-for="NewPassword" class="form-label">New Password</label> <input asp-for="NewPassword" class="form-control" placeholder="Enter your new password" /> <span asp-validation-for="NewPassword" class="text-danger"></span> </div> <div class="form-group mb-3"> <label asp-for="ConfirmPassword" class="form-label">Confirm New Password</label> <input asp-for="ConfirmPassword" class="form-control" placeholder="Re-enter your new password" /> <span asp-validation-for="ConfirmPassword" class="text-danger"></span> </div> <div class="d-grid"> <button type="submit" class="btn btn-primary">Update Password</button> </div> </form> </div> </div> </div> </div> </div>
Data Required for the Email Notifications:
In the Change Password Notification Email, we will include the time, the location, and the Device using which the user has changed the password for security reasons. So, let us first create the methods to retrieve the GEO Location and the Device information.
Get Location Information Using an IP Geolocation API
We need to use a third-party IP geolocation service like ipstack, ipinfo, or ip-api to get the user’s location. These services include city, region, country, and approximate latitude/longitude. So, please add the following private method to the Account controller, which will return the GEO location. The following code is self-explained, so please read the comment lines for a better understanding.
private async Task<string> GetLocationAsync(HttpContext httpContext) { try { // Get the client IP address var ipAddress = httpContext.Connection.RemoteIpAddress?.ToString(); // Check if the IP is a loopback address or local running if (string.IsNullOrEmpty(ipAddress) || ipAddress == "::1" || ipAddress == "127.0.0.1") { // Optionally use a service to get the external IP if local: // This can be useful for testing from development environments. using var client = new HttpClient(); ipAddress = await client.GetStringAsync("https://api.ipify.org"); } // Call the IP geolocation API using var httpClient = new HttpClient(); var response = await httpClient.GetStringAsync($"http://ip-api.com/json/{ipAddress}"); // Parse the response var locationData = JsonSerializer.Deserialize<Dictionary<string, object>>(response); if (locationData != null && locationData.TryGetValue("city", out var city) && locationData.TryGetValue("regionName", out var region) && locationData.TryGetValue("country", out var country)) { return $"{city}, {region}, {country}"; } return "Unknown Location"; } catch { return "Unknown Location"; } }
Get Device Information Using User-Agent Parsing
The User-Agent string is available in HttpContext.Request.Headers[“User-Agent”] contain information about the user’s browser and operating system. Use a library like UAParser for parsing. Please Install the UAParser Package by executing the following code in the Package Manager Console:
Install-Package UAParser
Once you install the above package, please add the following private method to the Account Controller to get the device information like the browser and operating system using which the password changed. The following code is self-explained, so please read the comment lines for a better understanding:
private string GetDeviceInfo(HttpContext httpContext) { try { // Retrieve the 'User-Agent' header from the incoming HTTP request. // The User-Agent string contains details about the client device's operating system, // browser, and other attributes. var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); // Check if the 'User-Agent' string is empty or null. If it is, return "Unknown Device" // because without a user agent, we cannot determine the device details. if (string.IsNullOrEmpty(userAgent)) { return "Unknown Device"; } // Use the UAParser library, which provides methods to parse the user agent string. // This library identifies the type of device, operating system, and browser from the string. var parser = Parser.GetDefault(); // Obtain a parser instance with default settings. // Parse the user agent string to extract detailed information about the client's device. var clientInfo = parser.Parse(userAgent); // Convert the operating system information extracted from the user agent to a string. // This typically includes the OS name and version. var os = clientInfo.OS.ToString(); // Operating System // Convert the browser information extracted from the user agent to a string. // This typically includes the browser name and version. var browser = clientInfo.UA.ToString(); // Browser // Concatenate and return the browser and operating system information in a readable format. return $"{browser} on {os}"; } catch { // If any error occurs during the processing of the user agent string, // return "Unknown Device". This catch block ensures that the method returns // a valid string even in case of an error. return "Unknown Device"; } }
Method to Send Password Change Notification Email:
We need to create one private to send the change password notification email. So, please add the following method to the Account controller to send the change password email. The following code is self-explained, so please read the comment lines for a better understanding:
private async Task SendPasswordChangedNotificationEmail(string email, ApplicationUser user, string location, string device) { // Create the email subject var subject = "Your Password Has Been Changed"; // Craft HTML message body var messageBody = $@" <div style=""font-family: Arial, Helvetica, sans-serif; font-size: 16px; color: #333; line-height: 1.5; padding: 20px;""> <h2 style=""color: #007bff; text-align: center;"">Password Change Notification</h2> <p style=""margin-bottom: 20px;"">Hi {user.FirstName} {user.LastName},</p> <p>We wanted to let you know that your password for your <strong>Dot Net Tutorials</strong> account was successfully changed.</p> <div style=""margin: 20px 0; padding: 10px; background-color: #f8f9fa; border: 1px solid #ddd; border-radius: 5px;""> <p><strong>Date and Time:</strong> {DateTime.UtcNow:dddd, MMMM dd, yyyy HH:mm} UTC</p> <p><strong>Location:</strong> {location}</p> <p><strong>Device:</strong> {device}</p> </div> <p>If you did not make this change, please reset your password immediately or contact support for assistance:</p> <p style=""margin-top: 30px;"">Thank you,<br />The Dot Net Tutorials Team</p> </div>"; // Send the email await emailSender.SendEmailAsync(email, subject, messageBody, IsBodyHtml: true); }
ChangePassword Post Action Method
When the user provides the details and clicks the Change Password button, the ChangePassword Post Action Method will handle that request. The POST action will:
- Validate the current password.
- Change the password if the validation is successful.
- Send a notification email to the user.
Please add the following ChangePassword Post Action Method within the Account Controller.
[Authorize] [HttpPost] public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model) { if (ModelState.IsValid) { //fetch the User Details var user = await userManager.GetUserAsync(User); if (user == null) { //If User does not exists, redirect to the Login Page return RedirectToAction("Login", "Account"); } // ChangePasswordAsync Method changes the user password var result = await userManager.ChangePasswordAsync(user, model.CurrentPassword, model.NewPassword); // The new password did not meet the complexity rules or the current password is incorrect. // Add these errors to the ModelState and rerender ChangePassword view if (result.Succeeded) { // Dynamically get location and device info var location = await GetLocationAsync(HttpContext); var device = GetDeviceInfo(HttpContext); // Send the notification email await SendPasswordChangedNotificationEmail(user.Email, user, location, device); // Upon successfully changing the password refresh sign-in cookie await signInManager.RefreshSignInAsync(user); //Then redirect the user to the ChangePasswordConfirmation view return RedirectToAction("ChangePasswordConfirmation", "Account"); } else { foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } } return View(model); }
ChangePasswordConfirmation Get Action Method
Optionally, you can create a confirmation view that informs the user their password has been successfully changed. Please add the following ChangePasswordConfirmation Get Action Method within the Account Controller. This action method will be executed once the user successfully changes the password.
[Authorize] [HttpGet] public IActionResult ChangePasswordConfirmation() { return View(); }
ChangePasswordConfirmation View
Please add a view named ChangePasswordConfirmation.cshtml within the Views/Account directory and then copy and paste the following code.
@{ ViewData["Title"] = "Change Password Confirmation"; } <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card shadow text-center"> <div class="card-header bg-success text-white"> <h4>Password Changed Successfully</h4> </div> <div class="card-body"> <p class="text-muted"> Your password has been updated successfully. For your security, please ensure your new password is kept confidential. </p> <hr /> <div class="d-grid"> <a asp-controller="Home" asp-action="Index" class="btn btn-secondary mt-2"> Return to Home </a> </div> </div> </div> </div> </div> </div>
Modify Layout Page:
We need to add a dropdown list with the name My Account, where we need to provide a link to change the password and sign out. This will be visible only when the user is logged in. So, modify our _Layout.cshtml file as follows:
@using Microsoft.AspNetCore.Identity @inject SignInManager<ApplicationUser> SignInManager <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - ASPNETCoreIdentityDemo</title> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/ASPNETCoreIdentityDemo.styles.css" /> </head> <body class="d-flex flex-column min-vh-100"> <header> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-light shadow-sm"> <div class="container-fluid"> <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index"> <img src="~/images/logo.png" alt="Logo" style="height:40px;"> </a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav me-auto"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="SecureMethod">Secure</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="NonSecureMethod">Non Secure</a> </li> @if (SignInManager.IsSignedIn(User) && (User.IsInRole("Admin") || User.IsInRole("SuperAdmin"))) { <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> Manage </a> <ul class="dropdown-menu" aria-labelledby="manageDropdown"> <li><a class="dropdown-item" asp-controller="Administration" asp-action="ListUsers">Users</a></li> <li><a class="dropdown-item" asp-controller="Administration" asp-action="ListRoles">Roles</a></li> </ul> </li> } </ul> <ul class="navbar-nav"> @if (SignInManager.IsSignedIn(User)) { <!-- Display username --> <li class="nav-item"> <span class="nav-link">Hello, @User.Identity.Name!</span> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="accountDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> My Account </a> <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="accountDropdown"> <li><a class="dropdown-item" asp-controller="Account" asp-action="ChangePassword">Change Password</a></li> <li> <form method="post" asp-controller="Account" asp-action="Logout" style="display:inline;"> <button type="submit" class="dropdown-item">Sign Out</button> </form> </li> </ul> </li> } else { <li class="nav-item"> <a class="nav-link text-dark" asp-controller="Account" asp-action="Register">Register</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-controller="Account" asp-action="Login">Login</a> </li> } </ul> </div> </div> </nav> </header> <div class="container flex-grow-1"> <main role="main" class="pb-3"> @RenderBody() </main> </div> <footer class="bg-light text-center text-lg-start border-top py-3 mt-auto"> <div class="container"> <p class="mb-0 text-muted"> © 2024 ASPNETCoreIdentityDemo - <a asp-area="" asp-controller="Home" asp-action="Privacy" class="text-decoration-none">Privacy</a> </p> </div> </footer> <script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> <script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> @await RenderSectionAsync("Scripts", required: false) </body> </html>
Adding Logo:
Please add the following image to the wwwroot/images folder and provide the name logo.png.
Now, run the application and test the change password functionality, and it should work as expected. This process ensures secure password changes by:
- Verifying the user’s identity through their current password.
- Sending a notification to the user upon successful password change.
- Safeguarding against unauthorized changes by notifying the user about the time, location, and device used.
By following this approach, we can ensure that the password change process in our ASP.NET Core Identity implementation is secure, user-friendly, and informative.
In the next article, I will discuss How to Add a Password to a Local Account Linked to an External Login in ASP.NET Core Identity. I explain How to Implement Change Password in ASP.NET Core Identity in this article. I hope you enjoy this article, Change Password in ASP.NET Core Identity.