Support us .Net Basics C# SQL ASP.NET Aarvi MVC Slides C# Programs Subscribe Download

Block login if email is not confirmed in asp.net core

Suggested Videos
Part 109 - ASP.NET Core facebook authentication | Text | Slides
Part 110 - ASP.NET Core secret manager | Text | Slides
Part 111 - Why email confirmation is important | Text | Slides

In this video we will discuss how to block a user from logging into asp.net core application if he has not yet confirmed his email.


If email address is not confirmed and if the user tries to login we want to display the validation error - Email not confirmed yet

asp.net core email confirmation


In ASP.NET core, application users are stored in AspNetUsers table. EmailConfirmed column in this table is used to determine if a given email address is confirmed.

block login if email is not confirmed in asp.net core

In this video we will discuss how to block a login if email address is not confirmed and in our next video we will implement email confirmation.

In ConfigureServices() method of the Startup class, set RequireConfirmedEmail property to true.

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>(options =>
    {
        options.SignIn.RequireConfirmedEmail = true;
    })
    .AddEntityFrameworkStores<AppDbContext>();
}


Let's say RequireConfirmedEmail property is set to true and the email address is not confirmed yet. If we now use the SignInManager service PasswordSignInAsync() method to sign-in the user we get NotAllowed as the result, even if we supply the correct username and password.

The same is true with ExternalLoginSignInAsync() method of SignInManager service. We use ExternalLoginSignInAsync() method to sign-in the user using an external login provider like Facebook, Google etc. If the email address associated with external login account is not confirmed, signin result will be NotAllowed.

The following Login action in AccountController, blocks the login and displays Email not confirmed yet error. 

[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl)
{
    model.ExternalLogins =
        (await signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

    if (ModelState.IsValid)
    {
        var user = await userManager.FindByEmailAsync(model.Email);

        if (user != null && !user.EmailConfirmed &&
                    (await userManager.CheckPasswordAsync(user, model.Password)))
        {
            ModelState.AddModelError(string.Empty, "Email not confirmed yet");
            return View(model);
        }

        var result = await signInManager.PasswordSignInAsync(model.Email,
                                model.Password, model.RememberMe, false);

        if (result.Succeeded)
        {
            if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("index", "home");
            }
        }

        ModelState.AddModelError(string.Empty, "Invalid Login Attempt");
    }

    return View(model);
}

This error message is displayed only, if the Email is not confirmed AND the user has provided correct username and password. 

If you are wondering, why do we need to check if the user has provided correct username and password. Well, this is to avoid account enumeration and brute force attacks. 

Let's understand, what might happen if we display this validation message - Email not confirmed yet, without checking if the provided email address and password combination is correct. An attacker might try random emails and as soon as he sees the message, Email not confirmed yet, he knows this is a valid email that could be used to login. He waits for couple of days until the email is confirmed by the actual owner, and can then try random passwords with that email address to gain access. 

In order to avoid these types of account enumeration and brute force attacks, display the validation error, only upon providing the correct email address and password combination.

We also want to block the login if an external login account (like Facebook, Google etc) is used and the email address associated with that external account is not confirmed. The section that blocks the login is commented.

[AllowAnonymous]
public async Task<IActionResult>
    ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    LoginViewModel loginViewModel = new LoginViewModel
    {
        ReturnUrl = returnUrl,
        ExternalLogins =
        (await signInManager.GetExternalAuthenticationSchemesAsync()).ToList()
    };

    if (remoteError != null)
    {
        ModelState.AddModelError(string.Empty,
            $"Error from external provider: {remoteError}");

        return View("Login", loginViewModel);
    }

    var info = await signInManager.GetExternalLoginInfoAsync();
    if (info == null)
    {
        ModelState.AddModelError(string.Empty,
            "Error loading external login information.");

        return View("Login", loginViewModel);
    }

    // Get the email claim from external login provider (Google, Facebook etc)
    var email = info.Principal.FindFirstValue(ClaimTypes.Email);
    ApplicationUser user = null;

    if (email != null)
    {
        // Find the user
        user = await userManager.FindByEmailAsync(email);

        // If email is not confirmed, display login view with validation error
        if (user != null && !user.EmailConfirmed)
        {
            ModelState.AddModelError(string.Empty, "Email not confirmed yet");
            return View("Login", loginViewModel);
        }
    }

    var signInResult = await signInManager.ExternalLoginSignInAsync(info.LoginProvider,
        info.ProviderKey, isPersistent: false, bypassTwoFactor: true);

    if (signInResult.Succeeded)
    {
        return LocalRedirect(returnUrl);
    }
    else
    {
        if (email != null)
        {
            if (user == null)
            {
                user = new ApplicationUser
                {
                    UserName = info.Principal.FindFirstValue(ClaimTypes.Email),
                    Email = info.Principal.FindFirstValue(ClaimTypes.Email)
                };

                await userManager.CreateAsync(user);
            }

            await userManager.AddLoginAsync(user, info);
            await signInManager.SignInAsync(user, isPersistent: false);

            return LocalRedirect(returnUrl);
        }

        ViewBag.ErrorTitle = $"Email claim not received from: {info.LoginProvider}";
        ViewBag.ErrorMessage = "Please contact support on Pragim@PragimTech.com";

        return View("Error");
    }
}

Please note : With the external login the user does not provide username and password to our application. Upon successful authentication, the user is redirected to the ExternalLoginCallback() action in our application. So we know the user is already authenticated and hence we display the validation error - Email not confirmed, without the need to check if the provided username and password combination is correct.

In our next video, we will discuss implementing email confirmation in asp.net core.

asp.net core tutorial for beginners

No comments:

Post a Comment

It would be great if you can help share these free resources