Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restrict access until user has confirmed email link

I am playing around the Identity.Samples example and found out that a user can still login without clicking on the email confirmation after registering. Is there a flag to turn on to restrict the user from logging in until he/she clicks the confirm link in his/her email? Or any extra code that I need to write to prevent this?

EDIT: Added the Login action code from the samples

    [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 SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
        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);
        }
    }
like image 829
icube Avatar asked Jun 16 '14 03:06

icube


3 Answers

If you want to require emailConfirmation, just add an additional check like this before you try the passwordSignIn:

        var user = await UserManager.FindByNameAsync(model.Email);
        if (user != null)
        {
               if (!await UserManager.IsEmailConfirmedAsync(user.Id)) return View("ErrorNotConfirmed");
        }
like image 194
Hao Kung Avatar answered Oct 18 '22 03:10

Hao Kung


Like JT I think you should check to make sure they are authenticated before letting them know they have to confirm the email address. Otherwise anyone could use your system to see if that email exists in your system. So I put Hao's code after signin.success

 var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: true);
    switch (result)
            {
                case SignInStatus.Success:
                    // Require the user to have a confirmed email before they can log on.
                    var user = await UserManager.FindByNameAsync(model.Email);
                    if (user != null)
                    {
                        if (!await UserManager.IsEmailConfirmedAsync(user.Id))
                        {                                                        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
                            ViewBag.errorMessage = "You must have a confirmed email to log on.";
                            return View("DisplayEmail");
                        }
                    }
                    return RedirectToLocal(returnUrl);
                case SignInStatus.LockedOut:
                    return View("Lockout");
                case SignInStatus.RequiresVerification:
                    return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
                case SignInStatus.Failure:
                default:
                    ModelState.AddModelError("", "Invalid login attempt.");
                    return View(model);
            }
like image 30
Dizzy Avatar answered Oct 18 '22 03:10

Dizzy


Based off of Hao's response I decided to write my own "SignInManager.SignInAsync" method to handle email confirmation before login. Not sure if this will help anyone else (or if there's a better way of doing this), but thought I'd share. One thing to note is that the functionality is a little different from Hao's suggestion. In order to get the "RequiresValidation" returned the user would have to first provide a valid username and password.

public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
    public ApplicationSignInManager(UserManager<ApplicationUser, string> userManager, IAuthenticationManager authenticationManager) 
        : base(userManager, authenticationManager)
    {
    }

    public async Task<SignInStatus> SignInAsync(string userName, string password, bool rememberMe)
    {
        var user = await UserManager.FindByNameAsync(userName);

        if (user == null) return SignInStatus.Failure;

        if (await UserManager.IsLockedOutAsync(user.Id)) return SignInStatus.LockedOut;

        if (!await UserManager.CheckPasswordAsync(user, password))
        {
            await UserManager.AccessFailedAsync(user.Id);
            if (await UserManager.IsLockedOutAsync(user.Id))
            {
                return SignInStatus.LockedOut;
            }

            return SignInStatus.Failure;
        }

        if (!await UserManager.IsEmailConfirmedAsync(user.Id))
        {
            return SignInStatus.RequiresVerification;
        }

        await base.SignInAsync(user, rememberMe, false);
        return SignInStatus.Success;
    }
}

Update 1: After some thought, I don't think this is right for 2FA. Based off of other site's 2FA implementation (Mandrill for one) they have users enter their user\pass, then additionally enter a passcode sent to their phone before they are authorized to enter the site. This is different from just verifying the email account before the user\pass is allowed to be used. I do like my implementation for Email Confirmation better since it doesn't divulge users accounts, but its not 2FA.

Update 2: Removed the check for GetTwoFactorEnabledAsync(). The above code is for preventing sign in until email confirmation is received and doesn't have anything to do with 2FA (you actually can't do 2FA with the above method since a token is never sent or validated before the user is signed in).

like image 6
jt000 Avatar answered Oct 18 '22 04:10

jt000