Содержание
Двухфакторная аутентификация позволяет дополнительно защитить ваш аккаунт от несанкционированного доступа. В общих чертах, двухфакторная аутентификация выглядит следующим образом: после ввода логина и пароля, пользователю на почту или по SMS направляется код подтверждения (одноразовый пароль), которые необходимо передать в приложение. После передачи кода подтверждения пользователь получает доступ ко всем возможностям, доступным его аккаунту. По умолчанию, двухэтапная аутентификация отключена для новых пользователей приложения ASP.NET Core MVC и в этой части мы рассмотрим процесс включения и использования двухэтапной аутентификации в ASP.NET Core.
Включение двухэтапной аутентификации для пользователя
Вернемся к нашему приложению. На данный момент, для всех новых пользователей двухэтапная аутентификация отключена. Убедиться в этом можно, посмотрев в базе данных таблицу пользователей:
Для того, чтобы заработала двухэтапная аутентификация, у объекта пользователя два флага должны быть равны true — это TwoFactorEnabled и EmailConfirmed (адрес электронной почты подтвержден). Что касается подтверждения адреса электронной почты, то мы это делали в предыдущей части и уже зарегистрировали пользователя с подтвержденным адресом электронной почты.
Чтобы включить двухфакторную аутентификацию для пользователя, нам необходимо внести изменения в базу данных, а именно в таблицу пользователей. Для этого изменим немного код компонента, который мы разрабытывали в одной из частей этой главы
а именно — создадим новую ссылку на страницу для включения двухфакторной аутентификации для конкретного пользователя. Добавим в файл Default.cshtml следующий код:
@using System.Security.Claims
@model ClaimsPrincipal
<div>
@if (Model.Identity.IsAuthenticated)
{
<p>Привет, @Model.Identity.Name</p>
<a href="/account/SignOut">Выйти из приложения</a><br/>
<a href="/account/updateuser/@Model.Identity.Name">Настройки аккаунта</a>
}
else
{
<p>Привет, Анонимус!</p>
<a href="/account/signin">Войти в приложение</a><br/>
<a href="/account/register">Зарегистрироваться</a>
}
</div>
По сравнению с тем, чтобы ло ранее в этом файле, мы добавили ссылку:
<a href="/account/updateuser/@Model.Identity.Name">Настройки аккаунта</a>
Для изменения настроек пользователя создадим новое представление в папке Areas/Account/Views/Account с названием UpdateUser.cshtml:
Добавим в файл следующий код:
@using AspMvcAuth.Models
@model ApplicationUser
@{
ViewData["Title"] = "Update user";
}
<h1 class="text-primary">Обновление настроек аккаунта</h1>
<form asp-area="Account" asp-action="UpdateUser" asp-controller="Account" method="post">
<div class="form-check">
<input type="hidden" asp-for="@Model.Id" />
<input asp-for="@Model.TwoFactorEnabled" class="form-check-input" type="checkbox" id="twoFactorAuth">
<label class="form-check-label" for="twoFactorAuth">
Использовать двухфакторную аутентификацию
</label>
<br/>
<button type="submit" class="btn btn-primary">Сохранить</button>
</form>
Здесь определена web-форма с помощью которой мы устанавливаем или снимаем флаг TwoFactorEnabled для объекта пользователя. Соответственно, в качестве модели выступает класс ApplicationUser.
Теперь добавим необходимые методы в контроллер AccountController:
[Route("{area}/{action}/{name}")]
[HttpGet]
public async Task<IActionResult> UpdateUser(string name)
{
var user = await _userManager.FindByNameAsync(name);
if (user == null)
return BadRequest();
return View(user);
}
[Route("{area}/{action}/{name}")]
[HttpPost]
public async Task<IActionResult> UpdateUser(ApplicationUser user)
{
var updatedUser = await _userManager.FindByIdAsync(user.Id.ToString());
if (updatedUser == null)
return BadRequest();
updatedUser.TwoFactorEnabled = user.TwoFactorEnabled;
await _userManager.UpdateAsync(updatedUser);
return LocalRedirect("/");
}
В первом методе UpdateUser() мы находим пользователя по его имени и отправляем объект в представление. Во втором методе, обрабатывающем POST-запрос, мы получаем в качестве параметра метода объект пользователя в котором содержится только Id пользователя и значение флага TwoFactorEnabled . Поэтому, вначале, мы получаем объект пользователя из хранилища:
var updatedUser = await _userManager.FindByIdAsync(user.Id.ToString());
а, затем, вносим изменения и обновляем запись пользователя в хранилище:
updatedUser.TwoFactorEnabled = user.TwoFactorEnabled; await _userManager.UpdateAsync(updatedUser);
Проверим работу. Запустим приложение и войдем с логином и паролем пользователя у которого подтвержден адрес электронной почты.
Зайдем в настройки аккаунта, включим двухфакторную авторизацию и сохраним результат:
Проверим результат в базе данных:
Как можно видеть на рисунке, флаг TwoFactorEnabled установлен в значение 1 (true) значит мы успешно включили двухфакторную аутентификацию для пользователя. Можно переходить к следующему этапу — разработке механизма двухфакторной аутентификации.
Использование двухфакторной аутентификации в приложении
Для того, чтобы использовать двухфакторную аутентификацию нам необходимо выполнить следующие шаги:
- Разработать представление в котором пользователю будет предложено ввести код подтверждения, который он будет получать по email
- Разработать необходимые действия контроллера, в которых будет формироваться, отправляться и проверяться код подтверждения
- Внести изменения в действие входа пользователя в приложение.
Начнем с самого простого — создадим необходимое представление. Для этого, в папке Areas/Account/Views/Account создадим файл представления с именем LoginTwoStep.cshtml и разместим в нем следующий код:
@using AspMvcAuth.Models
@model TwoFactor
@{
ViewData["Title"] = "Login Two Step";
}
<h1 class="bg-info text-white">Login Two Step</h1>
<div class="text-danger" asp-validation-summary="All"></div>
<p>Введите ваш код подтверждения</p>
<p>Код был отправлен на email, указанный при регистрации</p>
<form asp-action="LoginTwoStep" method="post">
<input type="hidden" asp-for="@Model.ReturnUrl" />
<div class="form-group">
<label>Код</label>
<input asp-for="@Model.TwoFactorCode" class="form-control" />
</div>
<button class="btn btn-primary" type="submit">Отправить код и войти</button>
</form>
В web-форме представления одно пле ввода — для кода подтверждения. После нажатия на кнопку «Отправить код и войти» должно выполнится действие контроллера с названием LoginTwoStep (его мы сейчас разработаем). При этом, в качестве модели представления указан класс TwoFactor, который выглядит максимально просто:
using System.ComponentModel.DataAnnotations;
namespace AspMvcAuth.Models
{
public class TwoFactor
{
[Required]
public string TwoFactorCode { get; set; } = "";
public string ReturnUrl { get; set; } = "/";
}
}
В свойстве TwoFactorCode мы будем передавать код подтверждения, а в ReturnUrl, при необходимости путь редиректа пользователя при успешной аутентификации. Теперь рассмотрим действия контроллера LoginTwoStep
[AllowAnonymous]
[HttpGet]
[Route("{area}/{action}")]
public async Task<IActionResult> LoginTwoStep(string email)
{
var user = await _userManager.FindByEmailAsync(email);
var token = await _userManager.GenerateTwoFactorTokenAsync(user, "Email");
_ = _emailHelper.SendEmailTwoFactorCode(user.Email, token);
return View(new TwoFactor());
}
[HttpPost]
[AllowAnonymous]
[Route("{area}/{action}")]
public async Task<IActionResult> LoginTwoStep(TwoFactor twoFactor)
{
if (!ModelState.IsValid)
{
return View(twoFactor.TwoFactorCode);
}
var result = await _signInManager.TwoFactorSignInAsync("Email", twoFactor.TwoFactorCode, false, false);
if (result.Succeeded)
{
return Redirect(twoFactor.ReturnUrl ?? "/");
}
else
{
ModelState.AddModelError("", "Неверный код подтверждения");
return View(twoFactor);
}
}
В первой версии метода, обрабатывающей GET-запрос, мы формируем код подтверждения и отправляем его пользователю по email, задействуя сервис отправки писем, который мы разработали в предыдущей части, при этом, сервис немного изменился. Так как необходимо отправлять письма с разными темами («подтверждение адреса», «код подтверждения»), то сервис теперь будет выглядеть следующим образом:
public class EmailHelper
{
public EmailHelperOptions Options { get; }
public EmailHelper(EmailHelperOptions options)
{
Options = options;
}
private bool Send(string userEmail, string message, string subject = "")
{
MailMessage mailMessage = new()
{
From = new MailAddress(Options.From)
};
mailMessage.To.Add(new MailAddress(userEmail));
if (string.IsNullOrEmpty(subject))
mailMessage.Subject = Options.Subject;
else
mailMessage.Subject = subject;
mailMessage.IsBodyHtml = true;
mailMessage.Body = message;
SmtpClient client = new()
{
DeliveryMethod = SmtpDeliveryMethod.Network,
Credentials = new System.Net.NetworkCredential(Options.Login, Options.Password),
Host = Options.Host,
Port = Options.Port,
EnableSsl = Options.EnableSSL
};
try
{
client.Send(mailMessage);
return true;
}
catch
{
return false;
}
}
public bool SendEmail(string userEmail, string confirmationLink)
{
return Send(userEmail, confirmationLink);
}
public bool SendEmailTwoFactorCode(string userEmail, string code)
{
return Send(userEmail, code, "Код подтверждения");
}
}
Что касается второй версии метода LoginTwoStep(), то здесь обрабатывается POST-запрос. На первом шаге проверяется наличие кода подтверждения как такового:
if (!ModelState.IsValid)
{
return View(twoFactor.TwoFactorCode);
}
Если код предоставлен, то мы пытаемся аутентифицировать пользователя, используя полученный код:
var result = await _signInManager.TwoFactorSignInAsync("Email", twoFactor.TwoFactorCode, false, false);
В зависимости от результата, мы либо перенаправляем пользователя на определенный URL, либо выдаем ошибку, что код подтверждения задан неверно:
if (result.Succeeded)
{
return Redirect(twoFactor.ReturnUrl ?? "/");
}
else
{
ModelState.AddModelError("", "Неверный код подтверждения");
return View(twoFactor);
}
Осталось внести изменения в действие входа пользователя в приложение (SignIn()). Теперь этот метод будет выглядеть следующим образом:
[Route("{area}/{action}")]
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> SignIn(LoginModel model)
{
ApplicationUser user = await _userManager.FindByNameAsync(model.Login);
bool emailStatus = await _userManager.IsEmailConfirmedAsync(user);
if (emailStatus == false)
{
ModelState.AddModelError(nameof(user.Email), "Email не подтвержден. Пожалуйста, пройдите по ссылке из отправленного вам письма");
return View("Login");
}
var result = await _signInManager.PasswordSignInAsync(model.Login, model.Password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
return LocalRedirect("/");
}
if (result.RequiresTwoFactor)
{
return RedirectToAction("LoginTwoStep", new { user.Email });
}
ModelState.AddModelError("", "Неверный логин или пароль");
return View("Login");
}
Так как у разных пользователей могут быть разные настройки аккаунта, то вначале, после проверки подтверждения адреса электронной почты мы пытаемся залогинить пользователя используя логин/пароль:
var result = await _signInManager.PasswordSignInAsync(model.Login, model.Password, false, lockoutOnFailure: false);
Далее, происходит следующее:
если у пользователя двухфакторная аутентификация отключена, то он сразу войдет в приложение и его перенаправят на главную страницу приложения:
if (result.Succeeded)
{
return LocalRedirect("/");
}
На этом этапе, для пользователей с двухфакторной аутентификацией объект result будет содержать ошибку — поэтому такие пользователи пройдут дальше до
if (result.RequiresTwoFactor)
{
return RedirectToAction("LoginTwoStep", new { user.Email });
}
то есть их мы перенаправим на наше новое представление, указав при этом их email в качестве параметра действия LoginTwoStep(). Если же пользователь доходит до этого шага и проверка показывает, что двухфакторная аутентификация выключена, то это может означать только то, что пользователь ввел неверный логин/пароль, о чем мы ему и сообщаем:
ModelState.AddModelError("", "Неверный логин или пароль");
return View("Login");
Осталось проверить работу нового механизма. Запустим приложение и попробуем войти в приложение, используя пользователя с включенной двухфакторной аутентификацией:
Как только будет нажата кнопка «Отправить» мы перейдем на представление для ввода кода подтверждения:
А на почту придет письмо с кодом подтверждения:
Вводим этот код в форму:
При попытке ввести неверный код мы получим сообщение:
Приложение работает корректно.
Итого
Двухфакторная аутентификация позволяет дополнительно защитить аккаунт от несанкционированного доступа. Для использования такой схемы аутентификации мы должны её включить в настройках конкретного пользователя. При этом, код подтверждения может приходить только на подтвержденную почту пользователя.








