Email Confirmation in ASP.NET Core Identity

Email Confirmation in ASP.NET Core Identity

In this article, I will discuss Email Confirmation in ASP.NET Core Identity, i.e., How to Confirm Email in ASP.NET Core Identity. Please read our previous article discussing Configuring Email Service in ASP.NET Core.

Why Is Email Confirmation Important?

Email confirmation is important in securing online accounts and verifying the user’s identity. Here are some key reasons why email confirmation is essential:

  • Verification of Identity: It ensures that the email address provided belongs to the person who entered it. This is crucial in preventing fraud and identity theft.
  • Verifying the Email Address: It ensures that the email address provided by the user is valid and accessible. This is crucial for websites and online services that rely on email communication for sending notifications, password resets, and other critical information.
  • Reduced Spam and Fake Accounts: Requiring email confirmation will help to prevent the creation of fake accounts. This is particularly important for public-facing applications where the integrity of user data is important.
  • Account Recovery: If a user forgets their password, the email address serves as a recovery point. Confirmed email addresses ensure that the recovery process is secure and that only the legitimate account owner can reset their password.
  • Improves Data Quality: For applications where the quality of user data is important, confirming email addresses helps ensure that the data collected is accurate and reliable. This is especially important for businesses that rely on email communication with their customers.
  • Security: Email confirmation acts as a first step in securing user accounts. It’s a way to ensure that the person creating the account is the actual owner of the email address. This is important because the email address often serves as a primary means of communication and identity verification. For instance, if a service requires an email confirmation for password resets, it prevents unauthorized access to user accounts. So, if an email address is not verified, it can lead to security vulnerabilities, such as unauthorized access to the user’s account.
  • Communication and Notifications: Verified email addresses are essential for effective communication. For applications that need to send out notifications, updates, or marketing emails, having a confirmed email list ensures that these communications reach the intended recipients.
  • Legal and Compliance: In some cases, confirming an email address is a legal requirement, especially in sectors like finance and healthcare.
  • User Trust and Credibility: Applications that take steps to verify user identities are often more trusted by users. This can enhance the credibility of the application and improve user engagement.
Example to Understand Why Email Confirmation is Important in ASP.NET Core:

When registering for an account with a website, we provide our email address. Upon registration, an email with a link is sent, which we should click to confirm that the provided email really belongs to us.

Until the email address is confirmed, your account will have limited functionality. Some websites might block you from signing in until the email address is confirmed. Letting users log in without a confirmed email is a security risk. Email confirmation is important for your security and the application’s security.

Let’s say, for example, a user registered with our application with the email john@example.com. His actual email is jon@example.com without the letter h. He made a typo and accidentally included the letter h in his email (john@example.com). Our application allowed the user to log in and set up his account by providing all the personal, financial, and other required details. This user is using the application as normal and so far, no problem.

After a few days, another user who owns the email john@example.com (with the letter h in the email) tries registering with our application. He cannot proceed with the registration because his email has already been used. So, he requests a password reset link, which will be sent to his email. He clicks on the link and changes the password.

There are 2 problems here.

  • The first user can no longer log in as the password changes. 
  • When the second user log-in he will have access to the first user’s personal, financial, and other details.

This is a huge concern from a security standpoint. The second user is able to hijack the first user account. We wouldn’t have been in this situation if the email was confirmed upon registration.

Account Confirmation Flow:

Implementing email confirmation functionality in an ASP.NET Core Identity application involves several steps, including sending a confirmation email, creating a view for email confirmation, and handling the confirmation logic.

The following is the Registration Page. Here, the User needs to provide the details and click on the Register button, as shown in the image below.

Account Confirmation Flow

When the user clicks the Register button, it will create a new user in the AspNetUsers table and display the following message.

Example to Understand Why Email Confirmation is Important in ASP.NET Core

If you verify the AspNetUsers table, you will see that the user is created, but the EmailConfirmed property value is 0, as shown in the image below.

Example to Understand Why Email Confirmation is Important in ASP.NET Core

Now, check your email id. The confirmation email should be sent, and if you open the email, you will see the following:

Email Confirmation in ASP.NET Core Identity

Once you click the above link, the account should be confirmed, and you will get the following message.

Email Confirmation in ASP.NET Core Identity

If you check the AspNetUsers table, you will see that the EmailConfirmed property value must be set to 1, as shown in the image below.

How to Confirm Email in ASP.NET Core Identity

Generate Email Confirmation Link

In the user registration process, generate an email confirmation link and send it to the user.

Generate Email Confirmation Token in ASP.NET Core Identity

In ASP.NET core, generating email confirmation tokens is straightforward. We need to use the GenerateEmailConfirmationTokenAsync() method of the UserManager service. This method takes one parameter. The user for whom we want to generate the email confirmation token. The syntax is given below:

var token = await userManager.GenerateEmailConfirmationTokenAsync(user);

Build the Email Confirmation Link

Once we have the token generated, build the email confirmation link. The user clicks this link to confirm his email. This link executes the ConfirmEmail action in the Account controller. The user ID and the email confirmation token are passed in the query string. Model binding in ASP.NET core maps the values from the query string parameters to the respective parameters on the ConfirmEmail action. The syntax is given below:

var confirmationLink = Url.Action(“ConfirmEmail”, “Account”,   new { UserId = user.Id, Token = token }, Request.Scheme);

The generated confirmation link would look like the following: https://localhost:44304/Account/ConfirmEmail?userId=987009e3-7f78-445e-8bb8-4400ba886550&token=CfDJ8Hpirs

The last parameter Request.Scheme returns the request protocol such as Http or Https. This parameter is required to generate the full absolute URL. A relative URL like the following will be generated if this parameter is not specified.

/Account/ConfirmEmail?userId=987009e3-7f78-445e-8bb8-4400ba886550&token=CfDJ8Hpirs

Confirming the Email

We need to use the ConfirmEmailAsync() method of the UserManager service to confirm the email. For this method, we need to pass 2 parameters. The user whose email we want to confirm and the email confirmation token. Upon successful email confirmation, this method sets the EmailConfirmed column to True in the AspNetUsers table. The syntax is given below:

var result = await userManager.ConfirmEmailAsync(user, token);

Note: In the token validation process, it will generate a token based on the user information, and then it will compare the token it received as part of the query string with the generated token. If both tokens match, then validation is successful. Let us proceed and implement this step by step.

Add ASP.NET Core Default Token Providers

First, we need to configure the token provider to generate the token. In the Program class, call the AddDefaultTokenProviders() method to add the ASP.NET Core default token providers that generate tokens for email confirmation, password reset, two-factor authentication, etc. So, please modify the AddIdentity service as follows:

builder.Services.AddIdentity<ApplicationUser, ApplicationRole>(
    options =>
    {
        // Password settings
        options.Password.RequireDigit = true;
        options.Password.RequiredLength = 8;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequireLowercase = true;
        options.Password.RequiredUniqueChars = 4;
    })
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();
Initialize the EmailSender Service in Account Controller:

We have created the EmailSender service in our previous article, which we will use to send the confirmation email. So, create a variable for the EmailSender service and then initialize that variable through the constructor of Account Controller as follows:

public class AccountController : Controller
{
    //userManager will hold the UserManager instance
    private readonly UserManager<ApplicationUser> userManager;

    //signInManager will hold the SignInManager instance
    private readonly SignInManager<ApplicationUser> signInManager;

    //ISenderEmail will hold the EmailSender instance
    private readonly ISenderEmail emailSender;

    // UserManager, SignInManager and EmailSender services are injected into the AccountController
    // using constructor injection
    public AccountController(UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager, ISenderEmail emailSender)
    {
        this.userManager = userManager;
        this.signInManager = signInManager;
        this.emailSender = emailSender;
    }

    //Rest of All Codes
}
Creating a Private Method to Send Confirmation Email:

Please add the following SendConfirmationEmail private method to the Account Controller, which will send the confirmation email to the user.

private async Task SendConfirmationEmail(string? email, ApplicationUser? user)
{
    //Generate the Token
    var token = await userManager.GenerateEmailConfirmationTokenAsync(user);

    //Build the Email Confirmation Link which must include the Callback URL
    var ConfirmationLink = Url.Action("ConfirmEmail", "Account",
    new { UserId = user.Id, Token = token }, protocol: HttpContext.Request.Scheme);

    //Send the Confirmation Email to the User Email Id
    await emailSender.SendEmailAsync(email, "Confirm Your Email", $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(ConfirmationLink)}'>clicking here</a>.", true);
}
Modifying the Register Post Action Method of Account Controller:

Once the registration is successful, we need to send the Confirmation Email. Then, we need to redirect to the Successful User Registration Page and show the message that your registration is successful if the user is not admin. So, modify the Register Post Action Method of the Account Controller as follows:

[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        // Copy data from RegisterViewModel to ApplicationUser
        var user = new ApplicationUser
        {
            UserName = model.Email,
            Email = model.Email,
            FirstName = model.FirstName,
            LastName = model.LastName
        };

        // Store user data in AspNetUsers database table
        var result = await userManager.CreateAsync(user, model.Password);

        // If user is successfully created, sign-in the user using
        // SignInManager and redirect to index action of HomeController
        if (result.Succeeded)
        {
            //Then send the Confirmation Email to the User
            await SendConfirmationEmail(model.Email, user);

            // If the user is signed in and in the Admin role, then it is
            // the Admin user that is creating a new user. 
            // So, redirect the Admin user to ListUsers action of Administration Controller
            if (signInManager.IsSignedIn(User) && User.IsInRole("Admin"))
            {
                return RedirectToAction("ListUsers", "Administration");
            }

            //If it is not Admin user, then redirect the user to RegistrationSuccessful View
            return View("RegistrationSuccessful");
        }

        // If there are any errors, add them to the ModelState object
        // which will be displayed by the validation summary tag helper
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    return View(model);
}
RegistrationSuccessful View:

Once the User is registered, we need to redirect the user to the RegistrationSuccessful view. So, create a view with the name RegistrationSuccessful.cshtml within the shared folder and then copy and paste the following code:

@{
    ViewData["Title"] = "Registration Successful";
}

<h3>
    Registration Successful
</h3>
<h5>
    Before you can Login, please confirm your email, by clicking on the confirmation link we have emailed you
</h5>
<a class="btn btn-primary" asp-controller="Account"
   asp-action="ResendConfirmationEmail">Resend Confirmation Email</a>

In the above code, we are displaying a message to the user that registration is successful, but before login, you need to confirm your email address by clicking on the link that sends your email ID. We have also provided one button for resending the Email Confirmation Link.

Create ConfirmEmail Action

Create an action in your Account Controller to handle email confirmation. This is the method that is going to be invoked whenever the user clicks on the link. So, please add the following ConfirmEmail action method in your Account Controller.

[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ConfirmEmail(string UserId, string Token)
{
    if (UserId == null || Token == null)
    {
        ViewBag.Message = "The link is Invalid or Expired";
    }

    //Find the User By Id
    var user = await userManager.FindByIdAsync(UserId);
    if (user == null)
    {
        ViewBag.ErrorMessage = $"The User ID {UserId} is Invalid";
        return View("NotFound");
    }

    //Call the ConfirmEmailAsync Method which will mark the Email as Confirmed
    var result = await userManager.ConfirmEmailAsync(user, Token);
    if (result.Succeeded)
    {
        ViewBag.Message = "Thank you for confirming your email";
        return View();
    }

    ViewBag.Message = "Email cannot be confirmed";
    return View();
}

Note: If the user exists and the token is valid, then the ConfirmEmailAsync method will update the EmailConfirmed column value to 1 means the email is confirmed.

Create ConfirmEmail View

Next, create a view with the name ConfirmEmail.cshtml within the Views/Account folder and copy and paste the following code. After the account is confirmed, this view is going to be rendered.

@{
    ViewData["Title"] = "ConfirmEmail";
}

<h3>@ViewBag.Message</h3>
ResendConfirmationEmail Get Action Method

In the RegistrationSuccessful view, we have provided the Resend Email button. Add the following ResendConfirmationEmail Get Action method within the Account Controller, which will be invoked when the user clicks on the Resend Email button. This method will render the Resend Account Confirmation Email view, where the user will enter his email for resending the confirmation email.

[HttpGet]
[AllowAnonymous]
public IActionResult ResendConfirmationEmail(bool IsResend = true)
{
    if (IsResend)
    {
        ViewBag.Message = "Resend Confirmation Email";
    }
    else
    {
        ViewBag.Message = "Send Confirmation Email";
    }
    return View();
}

Note: We will use the above action method and the following view for Resend and Send Email Confirmation Email purposes, and the IsResend parameter value will decide this.

ResendConfirmationEmail View:

Next, create a view with the name ResendConfirmationEmail.cshtml within the Views/Account folder and then copy and paste the following code:

@{
    ViewData["Title"] = @ViewBag.Message;
}

<div class="row">
    <div class="col-md-6">
        <h1>@ViewBag.Message</h1>
        <form asp-action="ResendConfirmationEmail" asp-controller="Account" method="post">
            <div class="form-group">
                <input type="email" name="email" class="form-control" placeholder="Enter your email" required />
            </div>
            <br/>
            <div class="form-group">
                <button class="btn btn-primary" type="submit">@ViewBag.Message</button>
            </div>
        </form>
    </div>
</div>
ResendConfirmationEmail Post Action Method

First, we need to find the user by their email or username. If the email is already confirmed, you can redirect the user or inform them accordingly. If the email is not confirmed, generate a new confirmation token and send it via email. So, please add the following ResendConfirmationEmail Post Action method within the Account Controller.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ResendConfirmationEmail(string Email)
{
    var user = await userManager.FindByEmailAsync(Email);
    if (user == null || await userManager.IsEmailConfirmedAsync(user))
    {
        // Handle the situation when the user does not exist or Email already confirmed.
        // For security, don't reveal that the user does not exist or Email is already confirmed
        return View("ConfirmationEmailSent");
    }

    //Then send the Confirmation Email to the User
    await SendConfirmationEmail(Email, user);

    return View("ConfirmationEmailSent");
}
ConfirmationEmailSent View:

Create a view named ConfirmationEmailSent.cshtml within the Views/Shared directory, and then copy and paste the following code:

@{
    ViewData["Title"] = "ConfirmationEmailSent";
}

<h1>Confirmation Email Sent</h1>
<h3>If Any Account Exists with the Given Email Id, then Email Confirmation Link is Sent.</h3>
Why is the Email Confirmation Token Not Stored in the AspNetUserTokens Identity Table?

In ASP.NET Core Identity, the AspNetUserTokens table typically stores tokens for various purposes, like authentication, two-factor authentication, or external login providers. However, the email confirmation token is not stored in the AspNetUserTokens table by default, and here are a few reasons why:

  • Security Considerations: Storing the email confirmation token in a database can pose a security risk. If the database is compromised, an attacker could access these tokens and use them to confirm email addresses without the user’s consent.
  • One-Time Use and Short Lifespan: Email confirmation tokens are typically one-time and have a very short lifespan. They are meant to be used soon after they are generated. Storing them in the database might not be efficient or necessary, as they become invalid quickly.
  • Stateless Approach: Generating tokens without storing them in the database keeps the process stateless. The token is generated based on user-specific data (like the user’s ID and email) and a secret key. When a user submits a token for validation, the system can re-generate it using the same data and secret key to verify its authenticity. This approach reduces database dependency and simplifies the token management process.
  • Token Verification Process: In ASP.NET Core Identity, the token verification process typically involves generating a new token using the same parameters and comparing it with the submitted token. If both matches, the token is considered valid. This process doesn’t require storing the token in the database.
  • Simplification and Performance: Not storing these tokens can simplify the design of the Identity system and improve performance. It reduces the need for additional database operations, like storing and retrieving tokens.

In the next article, I will discuss How to Block Login if Email is not Confirmed in ASP.NET Core Identity. In this article, I explain How to Implement Email Confirmation in ASP.NET Core Identity. I hope you enjoy this article, Email Confirmation in ASP.NET Core Identity.

Leave a Reply

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