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
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.
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;
}
}
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);
Using the ASP.NET Identity 2.0 Samples.
//
// 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);
}
}
public enum SignInStatus
{
Success,
EmailNotConfirmed,
LockedOut,
RequiresTwoFactorAuthentication,
Failure
}
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;
}
@{
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With