prevent users without confirmed email from logging in ASP.Net MVC with Identity 2

Maybe its a little late but I hope it may help others.

Add this

var userid = UserManager.FindByEmail(model.Email).Id;
        if (!UserManager.IsEmailConfirmed(userid))
        {
            return View("EmailNotConfirmed");
        }

before

var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);

The first block of code just checks if the email in the model exists in the database and gets it's id to check if it is not confirmed and if so returns a view to the user wich says so and if it is confirmed just lets the user sign in.

And delete your changes to the result switch like this

switch (result)
        {
            case SignInStatus.Success:
                    return RedirectToLocal(returnUrl);
            case SignInStatus.LockedOut:
                return View("Lockout");
            case SignInStatus.RequiresVerification:
                return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
            case SignInStatus.Failure:
            default:
                ModelState.AddModelError("", "Invalid login attempt.");
                return View(model);
        }

Instead of moving to another page, why not finish this one and redirect to the right action / view:

if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
    return RedirectToAction("ConfirmEmailAddress", new { ReturnUrl = returnUrl });
}

You do need an action (and possibly a view) with the name ConfirmEmailAddress though.


There is a solution, which may not be the best approach, but it works. First let me try to clarify why your approach did not work.

In one of the comments it is mentioned, the AuthenticationManager uses cookies. In order to update a cookie you need to send it to the client, using another page. That is why TransferRequest is not going to work.

How to handle the emailverification? The strategy I used:

1) On SignInStatus.Success this means that the user is logged in.

2) When email is not confirmed: send an email to the used e-mailaddress. This is safe since the user already signed in. We are just blocking further access until the e-mail is verified. For each time a user tries to login without having validated the email, a new email (with the same link) is sent. This could be limited by keeping track of the number of sent emails.

3) We cannot use LogOff: this is HttpPost and uses a ValidateAntiForgeryToken.

4) Redirect to a page (HttpGet, authorization required) that displays the message that an e-mail has been sent. On entering sign out the user.

5) For other validation errors, redirect to another method to sign out (HttpGet, authorization required). No view needed, redirect to the login page.

In code: update the code in AccountController.Login to:

        case SignInStatus.Success:
        {
            var currentUser = UserManager.FindByNameAsync(model.Email);
            if (!await UserManager.IsEmailConfirmedAsync(currentUser.Id))
            {
                // Send email
                var code = await UserManager.GenerateEmailConfirmationTokenAsync(currentUser.Id);
                var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = currentUser.Id, code = code}, protocol: Request.Url.Scheme);
                await UserManager.SendEmailAsync(currentUser.Id, "Confirm your account", string.Format("Please confirm your account by clicking this link: <a href=\"{0}\">link</a>", callbackUrl));
                // Show message
                return RedirectToAction("DisplayEmail");
            }
            // Some validation
            if (true)
            {
                return RedirectToAction("SilentLogOff");
            }
            return RedirectToLocal(returnUrl);
        }

Add methods to AccountController:

// GET: /Account/SilentLogOff
[HttpGet]
[Authorize]
public ActionResult SilentLogOff()
{
    // Sign out and redirect to Login
    AuthenticationManager.SignOut();
    return RedirectToAction("Login");
}

// GET: /Account/DisplayEmail
[HttpGet]
[Authorize]
public ActionResult DisplayEmail()
{
    // Sign out and show DisplayEmail view
    AuthenticationManager.SignOut();
    return View();
}

DisplayEmail.cshtml

@{
    ViewBag.Title = "Verify e-mail";
}

<h2>@ViewBag.Title.</h2>
<p class="text-info">
    Please check your email and confirm your email address.
</p>

You'll notice that the user cannot reach other pages until email is verified. And we are able to use the features of the SignInManager.

There is one possible problem (that I can think of) with this approach, the user is logged in for the time that the email is sent and the user is being redirected to the DisplayMessage view. This may not be a real problem, but it shows that we are not preventing the user from logging in, only denying further access after logging in by automatically logging out the user.

=== Update ====

Please note that exceptions have to be handled properly. The user is granted access and then access is revoked in this scenario. But in case an exception occurs before signing out and this exception was not catched, the user remains logged in.

An exception can occur when the mailserver is not available or the credentials are empty or invalid.

===============