Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent login when EmailConfirmed is false

The newest ASP.NET identity bits (2.0 beta) include the foundation for confirming user email addresses. The NuGet package "Microsoft Asp.Net Identity Samples" contains a sample showing this flow. But in that sample, even when EmailConfirmed = false, there is no different behavior in the user experience.

How can I prevent users from being able to login when their email address is not yet confirmed? I understand that I can have the users log in regardless and then perform the check on the EmailConfirmed field, but it seems like it would be much more efficient if I could prevent the user from successfully logging in at all when EmailConfirmed == false

like image 665
BenjiFB Avatar asked Mar 03 '14 18:03

BenjiFB


4 Answers

You need to add a few lines to the Login action (POST method) to verify that the user has confirmed their email. The method you want to check is UserManager.IsEmailConfirmed. Here is what your Login action will look like.

    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            var user = await UserManager.FindByNameAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", "Invalid login attempt.");
                return View(model);
            }
            //Add this to check if the email was confirmed.
            if (!await UserManager.IsEmailConfirmedAsync(user.Id))
            {
                ModelState.AddModelError("", "You need to confirm your email.");
                return View(model);
            }
            if (await UserManager.IsLockedOutAsync(user.Id))
            {
                return View("Lockout");
            }
            if (await UserManager.CheckPasswordAsync(user, model.Password))
            {
                // Uncomment to enable lockout when password login fails
                //await UserManager.ResetAccessFailedCountAsync(user.Id);
                return await LoginCommon(user, model.RememberMe, returnUrl);
            }
            else
            {
                // Uncomment to enable lockout when password login fails
                //await UserManager.AccessFailedAsync(user.Id);
                ModelState.AddModelError("", "Invalid login attempt.");
                return View(model);
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

In this example I just return the user to the login view and display an error message. You could return them to another view that provides more details on the next steps to confirm their email, or even give them the option to resend the email confirmation.

like image 124
Kevin Junghans Avatar answered Oct 21 '22 20:10

Kevin Junghans


in ApplicationOAuthProvider.cs

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

            ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);

            if (user == null)
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            }

            if (!user.EmailConfirmed)
            {
                context.SetError("invalid_grant", "Account pending approval.");
                return;
            }
        }
like image 21
ileff Avatar answered Oct 21 '22 19:10

ileff


I just modified the case when the login could be successfull:

case SignInStatus.Success:
    if (!await UserManager.IsEmailConfirmedAsync((await UserManager.FindByNameAsync(model.Email)).Id))
    {
        Session.Abandon();
        AuthenticationManager.SignOut();
        ModelState.AddModelError("", "You need to confirm your email address");
        return View(model);
    }
    return RedirectToLocal(returnUrl);
like image 10
e4rthdog Avatar answered Oct 21 '22 20:10

e4rthdog


Using the ASP.NET Identity 2.0 Samples.

1. Update the POST Login action in Account controller.

    //
    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        // This doen't count login failures towards lockout only two factor authentication
        // To enable password failures to trigger lockout, change to shouldLockout: true
        var result = await SignInHelper.PasswordSignIn(model.Email, model.Password, model.RememberMe, shouldLockout: false);
        switch (result)
        {
            case SignInStatus.Success:
                return RedirectToLocal(returnUrl);
            case SignInStatus.EmailNotConfirmed:
                return View("EmailNotConfirmed");
            case SignInStatus.LockedOut:
                return View("Lockout");
            case SignInStatus.RequiresTwoFactorAuthentication:
                return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
            case SignInStatus.Failure:
            default:
                ModelState.AddModelError("", "Invalid login attempt.");
                return View(model);
        }
    }

2. Update the SignInStatus enum in IdentityConfig.cs.

public enum SignInStatus
{
    Success,
    EmailNotConfirmed,
    LockedOut,
    RequiresTwoFactorAuthentication,
    Failure
}

3. Update the PasswordSignIn method in IdentityConfig.cs.

    public async Task<SignInStatus> PasswordSignIn(string userName, string password, bool isPersistent, bool shouldLockout)
    {
        var user = await UserManager.FindByNameAsync(userName);
        if (user == null)
        {
            return SignInStatus.Failure;
        }
        if (!(await UserManager.IsEmailConfirmedAsync(user.Id)))
        {
            return SignInStatus.EmailNotConfirmed;
        }
        if (await UserManager.IsLockedOutAsync(user.Id))
        {
            return SignInStatus.LockedOut;
        }
        if (await UserManager.CheckPasswordAsync(user, password))
        {
            return await SignInOrTwoFactor(user, isPersistent);
        }
        if (shouldLockout)
        {
            // If lockout is requested, increment access failed count which might lock out the user
            await UserManager.AccessFailedAsync(user.Id);
            if (await UserManager.IsLockedOutAsync(user.Id))
            {
                return SignInStatus.LockedOut;
            }
        }
        return SignInStatus.Failure;
    }

4. Add a new View "EmailNotConfirmed.cshtml".

@{
    ViewBag.Title = "Email not confirmed";
}

<h2>You have not confirmed your email.</h2>

<p>Please click the link in the email we sent you to confirm your email.</p>

<p>todo: Add a "resend confirmation email" button here.</p>
like image 3
PussInBoots Avatar answered Oct 21 '22 20:10

PussInBoots