Add Password to Local Account Linked to External Login

Add Password to Local Account Linked to External Login in ASP.NET Core Identity

In this article, I will discuss How to Add a Password to a Local Account Linked to an External Login in ASP.NET Core Identity. Please read our previous article discussing How to Implement the Change Password in ASP.NET Core Identity.

Add Password to Local Account Linked to External Login

Now, we will see how to Add a Password to a Local Account Linked to an External Login so that the user can log in using both external authentication and his local username and password. Let us first understand the flow.

The following is our login page. From this page, the user can log in using his local or external accounts. Let’s assume the user logs in using his Google External account by clicking on the Google button, as shown in the image below.

Add Password to Local Account Linked to External Login

After login, if you verify the AspNetUserLogins table, you will see the external provider information is stored, as shown in the image below. The AspNetUserLogins table in ASP.NET Core Identity stores the external user authentication details.

Add Password to Local Account Linked to External Login

Now, if you check the AspNetUsers table, then you will see the PasswordHash column value is Null for the above External Login user, as shown in the image below.

Add Password to Local Account Linked to External Login

Now, our requirement is to add the password to the local account of the external user so that the external user can log into our system using both the external login and the local username and password. 

Add Password to Local Account Linked to External Login:

To add a password to a local account that’s linked to an external login in ASP.NET Core Identity, we need to follow the below steps.

  • User Authentication: Ensure the user is authenticated through their external login.
  • Retrieve User Information: Once authenticated, retrieve the user’s information from the ASP.NET Core Identity database.
  • Check for Existing Password: Check if the user already has a password set. This can be done using the UserManager.HasPasswordAsync method.
  • Add Password UI: If the user doesn’t have a password, provide a UI for them to input a new password. This typically involves creating a view with a form for password input.
  • Password Validation: Validate the password input according to the password policy configuration (e.g., length, complexity).
  • Update User Account: Use UserManager.AddPasswordAsync method to add the password to the user’s account. This method will hash the password and store it in the database.
  • Handle Success or Failure: Depending on the result of AddPasswordAsync, we will redirect the user to a confirmation page or show error messages.

Note: Once the password is set, the user should be able to sign in using the local account (with the new password) and the external login.

How Do We Add a Password to the Local Account Linked to the External Login?

We need to display either the Set Password or Change Password link based on whether the logged-in user has set a local password or is using external authentication without a local password. If the user sets the password, we need to display the Change Password link; otherwise, we need to display the Set Password link within the My Account dropdown list, as shown in the image below. Let us assume the user is logged in using external authentication and has not set a local password, and in that case, we are providing the option to add a local password.

How Do We Add a Password to the Local Account Linked to the External Login?

Once the user clicks on the Set Password button, the Set Password page will open. Here, you can see that we don’t have that Current or Old password text box, and it makes sense because this is an external user, and he has not set the password earlier.

How Do We Add a Password to the Local Account Linked to the External Login?

Once the user provides the password, confirms the password, and clicks on the Set Password button, if the provided password is valid, then the password will be set in the AspNetUsers table. Then, it will redirect the user to the following confirmation page.

How Do We Add a Password to the Local Account Linked to the External Login?

Now, if you verify the above user detail in the AspNetUsers table, then you will see the Pashwordhash column is set as shown in the image below.

How Do We Add a Password to the Local Account Linked to the External Login?

Now, the user can log in using external authentication and a local user account. Once the password is set for the external user, then you will see the Change Password button instead of Set Password as shown in the below image as we have set a local password for the external user:

Add Password to Local Account Linked to External Login Work Flow in ASP.NET Core Identity

Let us proceed and see how we can Add a Password to a Local Account Linked to an External Login in ASP.NET Core Identity.

Set Password View Model

First, create a ViewModel to represent the data needed to add a password. Add a class file named SetPasswordViewModel.cs and copy and paste the following code. As the external user doesn’t have to store the password in the database, we don’t need the current password in the Set Password View. So, the View Model has two properties: Password and ConfirmPassword.

using System.ComponentModel.DataAnnotations;
namespace ASPNETCoreIdentityDemo.Models
{
    public class SetPasswordViewModel
    {
        [Required(ErrorMessage = "Password is required.")]
        [DataType(DataType.Password, ErrorMessage = "Invalid password format.")]
        [Display(Name = "New Password")]
        public string Password { get; set; }

        [Required(ErrorMessage = "Please confirm your password.")]
        [DataType(DataType.Password, ErrorMessage = "Invalid password format.")]
        [Display(Name = "Confirm New Password")]
        [Compare("Password", ErrorMessage = "The new password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }
}

SetPassword Get Action Method

Please add the following SetPassword Get Action Method within the Account Controller. This action method will display the Set Password view, which allows the external user to add his/her password.

[Authorize]
[HttpGet]
public async Task<IActionResult> SetPassword()
{
    //First Fetch the User Details
    var user = await userManager.GetUserAsync(User);

    //Then Check whether the User Already has a Password
    var userHasPassword = await userManager.HasPasswordAsync(user);

    //If the user already has a password, redirect to the ChangePassword Action method
    if (userHasPassword)
    {
        return RedirectToAction("ChangePassword", "Account");
    }

    //If the user has no password, then display the Set Password view
    return View();
}

In the above code, we first check whether the user already has a password or not. If the user already has a password, we are redirecting the user to Change Password action method, else, we are rending the Set Password view.

Modifying the ChangePassword Get Action Method:

Similarly, we also need to change the logic of the ChangePassword Get Action method. So, please modify the ChangePassword Get Action method of the Account Controller as follows. Here, we are checking whether the user already has a password or not. If the user already has a password, we render the Change Password view. Otherwise, we are redirecting to the Set Password action method.

[Authorize]
[HttpGet]
public async Task<IActionResult> ChangePassword()
{
    // First Fetch the User Details
    var user = await userManager.GetUserAsync(User);

    //Then Check whether the User Already has a Password
    var userHasPassword = await userManager.HasPasswordAsync(user);

    //If the user has no password, redirect to the SetPassword Action method
    if (!userHasPassword)
    {
        return RedirectToAction("SetPassword", "Account");
    }

    //If the user has already password, then display the Change Password view
    return View();
}
Set Password View

Add a view named SetPassword.cshtml within the Views/Account folder, and then copy and paste the following code. This is the view where the user will input the new password and confirm password.

@model SetPasswordViewModel

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

<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <div class="card shadow">
                <div class="card-header text-center bg-primary text-white">
                    <h4>Set Your Password</h4>
                </div>
                <div class="card-body">
                    <p class="text-info text-center mb-4">
                        You are currently signed in using an external account. To enhance your account security,
                        you can set a local password that allows you to log in directly with your email.
                    </p>
                    <form method="post" asp-action="SetPassword" asp-controller="Account">
                        <div asp-validation-summary="All" class="text-danger"></div>

                        <div class="form-group mb-3">
                            <label asp-for="Password" class="form-label">New Password</label>
                            <input asp-for="Password" class="form-control" placeholder="Enter a strong password" />
                            <span asp-validation-for="Password" class="text-danger"></span>
                        </div>

                        <div class="form-group mb-4">
                            <label asp-for="ConfirmPassword" class="form-label">Confirm Password</label>
                            <input asp-for="ConfirmPassword" class="form-control" placeholder="Re-enter your password" />
                            <span asp-validation-for="ConfirmPassword" class="text-danger"></span>
                        </div>

                        <div class="d-grid">
                            <button type="submit" class="btn btn-primary btn-lg">Set Password</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

SetPassword Post Action Method

Please add the following SetPassword Post Action Method within the Account Controller. This action method will set the password of an external user.

[Authorize]
[HttpPost]
public async Task<IActionResult> SetPassword(SetPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await userManager.GetUserAsync(User);
        if (user == null)
        {
            ModelState.AddModelError(string.Empty, "Invalid User.");
            return View();
        }

        //Call the AddPasswordAsync method to set the new password without old password
        var result = await userManager.AddPasswordAsync(user, model.Password);

        // Handle the failure scenario
        if (!result.Succeeded)
        {
            //fetch all the error messages and display on the view
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError(string.Empty, error.Description);
            }
            return View();
        }

        // Handle Success Scenario
        // refresh the authentication cookie to store the updated user information
        await signInManager.RefreshSignInAsync(user);

        //redirecting to the AddPasswordConfirmation action method
        return RedirectToAction("SetPasswordConfirmation", "Account");
    }

    return View();
}
SetPasswordConfirmation Get Action Method

Next, we need to create a confirmation view that informs the user their password has been successfully added. Please add the following SetPasswordConfirmation Get Action Method within the Account Controller. This action method will be executed once the external user has successfully set the password.

[Authorize]
[HttpGet]
public IActionResult SetPasswordConfirmation()
{
    return View();
}
SetPasswordConfirmation View

Please add a view named SetPasswordConfirmation.cshtml view within the Views/Account directory and then copy and paste the following code.

@{
    ViewData["Title"] = "Set Password Confirmation";
}

<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <div class="card shadow">
                <div class="card-header bg-success text-white text-center">
                    <h4>Password Successfully Set</h4>
                </div>
                <div class="card-body text-center">
                    <p class="text-success fs-5">
                        Congratulations! You have successfully set a local password for your account.
                    </p>
                    <p class="text-muted">
                        You can now log in using either your local account credentials or continue using your external authentication provider.
                    </p>
                    <div class="mt-4">
                        <a href="/" class="btn btn-primary btn-lg me-2">Go to Home</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
Modifying the Layout Page:

To conditionally display either the “Change Password” or “Set Password” button based on the user’s authentication method and whether they have a local password set, we can use the UserManager and SignInManager services provided by ASP.NET Core Identity.

  • First, we need to inject the UserManager<ApplicationUser> service into our Layout view. This service allows us to interact with user-related operations, such as checking if a user has a password set.
  • Then, we need to use a Razor code block to asynchronously retrieve the current user and determine whether they have a password set. Based on this, Modify the My Account dropdown menu to conditionally display the “Set Password” or “Change Password” link.

So, please modify the _Layout.cshtml view as follows:

@using Microsoft.AspNetCore.Identity
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager

<!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">
                                    @{
                                        // Fetch the current user
                                        var currentUser = await UserManager.GetUserAsync(User);

                                        // Check if the user has a password
                                        bool hasPassword = await UserManager.HasPasswordAsync(currentUser);

                                        if (!hasPassword)
                                        {
                                            // Show "Set Password" link if no local password is set
                                            <li>
                                                <a class="dropdown-item" asp-controller="Account" asp-action="SetPassword">Set Password</a>
                                            </li>
                                        }
                                        else
                                        {
                                            // Show "Change Password" link if a local password is set
                                            <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">
                &copy; 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>

With the above changes, run the application and test the functionalities. It should work as expected.

When Should We Add Password to Local Account Linked to External Login?

Adding a password to a local account linked to an external login is important for maintaining security and flexibility. The following are key scenarios where adding a password to your local account is beneficial:

  • Enhanced Security: If the external service (such as Google, Facebook, or another provider) experiences a security breach, having a separate password for your local account provides an additional layer of security. This way, even if your external login credentials are compromised, your local account remains secure.
  • Backup Access: If you lose access to your external account (for reasons like forgotten credentials, account suspension, or provider issues), a local password will ensure you can still access your account. Without a local password, you may be entirely dependent on the external login service, which could lead to account lockout.
  • Separation of Concerns: If you prefer to keep your external login credentials separate from your local account for privacy or security reasons, adding a password to your local account can help. This gives you more control over your credentials and reduces the dependency on a third-party provider.
  • Transitioning Away from External Login: If you plan to stop using the external login service in the future, adding a password to your local account prepares your account for a smooth transition. This ensures you won’t be left without access to your account once you discontinue using the external service.
  • Different Privilege Levels: If your local account has higher privileges or accesses more sensitive information than your external login, a local password can act as an additional safeguard. This is especially important in cases where you manage sensitive data or require different levels of access within the application.

In the next article, I will discuss How to Integrate SMS Service in ASP.NET Core. In this article, I explain How to Add a Password to a Local Account Linked to an External Login in ASP.NET Core Identity. I hope you enjoy this article, Add Password to Local Account Linked to External Login in ASP.NET Core Identity.

Leave a Reply

Your email address will not be published. Required fields are marked *