Ограничьте доступ, пока пользователь не подтвердит ссылку по электронной почте

Я играю вокруг примера Identity.Samples и узнал, что пользователь все равно может войти без нажатия на подтверждение по электронной почте после регистрации. Есть ли флаг для включения, чтобы запретить пользователю входить в систему до тех пор, пока он/она не нажмет ссылку подтверждения в своем электронном письме? Или любой дополнительный код, который мне нужно написать, чтобы предотвратить это?

EDIT: добавлен код действия входа из образцов

    [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);
        }
    }

Ответы

Ответ 1

Если вы хотите запросить подтверждение по электронной почте, просто добавьте дополнительную проверку, как это, прежде чем пытаться использовать парольSignIn:

        var user = await UserManager.FindByNameAsync(model.Email);
        if (user != null)
        {
               if (!await UserManager.IsEmailConfirmedAsync(user.Id)) return View("ErrorNotConfirmed");
        }

Ответ 2

Основываясь на ответе Хао, я решил написать свой собственный метод SignInManager.SignInAsync для обработки подтверждения электронной почты до входа в систему. Не уверен, что это поможет кому-то еще (или, если есть лучший способ сделать это), но подумал, что я поделюсь. Следует отметить, что функциональность немного отличается от предложения Hao. Чтобы вернуть "RequiresValidation", пользователь должен сначала указать действительное имя пользователя и пароль.

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;
    }
}

Обновление 1. Подумав, я не думаю, что это правильно для 2FA. Исходя из реализации другого сайта 2FA (Mandrill для одного), у них есть пользователи, которые вводят свой пользователь \pass, затем дополнительно вводят код доступа, отправленный на свой телефон, прежде чем они смогут войти на сайт. Это отличается от просто проверки учетной записи электронной почты до того, как пользователь \pass разрешен для использования. Мне больше нравится моя реализация для подтверждения электронной почты, так как она не разглашает учетные записи пользователей, но ее не 2FA.

Обновление 2. Удалена проверка GetTwoFactorEnabledAsync(). Вышеприведенный код предназначен для предотвращения входа в систему до получения подтверждения по электронной почте и не имеет ничего общего с 2FA (вы фактически не можете делать 2FA с помощью вышеуказанного метода, поскольку токен никогда не отправляется или не проверяется до того, как пользователь будет подписан).

Ответ 3

Как и JT, я думаю, вы должны проверить, чтобы убедиться, что они прошли проверку подлинности, прежде чем сообщить им, что они должны подтвердить адрес электронной почты. В противном случае любой может использовать вашу систему, чтобы узнать, существует ли это письмо в вашей системе. Поэтому я поставил код Hao после 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);
            }