Содержание
Подтверждение адреса электронной почты позволяет предположить, что с высокой долей вероятности, зарегистрированный пользователь является реальным и указанный адрес электронной почты рабочий. В этой части мы рассмотрим процесс подтверждения адреса электронной почты для новых пользователей.
Как включить подтверждение адреса электронной почты в ASP.NET Core Identity
В нашем приложение, которое мы разрабатываем, подтверждение адреса электронной почты отключено. Убедиться в этом можно, посмотрев в базе данных таблицу пользователей:
Чтобы включить подтверждение адреса электронной почты необходимо внести изменение в конфигурацию сервиса аутентификации:
builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedEmail = true; //включаем подтверждение адреса электронной почты
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireDigit = true;
options.Password.RequiredLength = 10;
})
Также, в качестве тестирования новой для нас опции, мы можем вынести подтверждение адреса электронной почты в настройки, например, в файл appsettings.json следующим образом:
{
"RequireConfirmedEmail": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
и теперь использовать такую настройку сервиса аутентификации:
var requireEmailConfirmed = builder.Configuration.GetValue<bool>("RequireConfirmedEmail");
builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedEmail = requireEmailConfirmed; //включаем подтверждение адреса электронной почты
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireDigit = true;
options.Password.RequiredLength = 10;
})
.AddRoles<ApplicationRole>() //включаем поддержку ролей
.AddEntityFrameworkStores<ApplicationContext>();
Теперь нам необходимо разработать необходимые процедуры подтверждения адреса электронной почты пользователя. Для этого нам потребуется внести изменения в действия контроллера AccountController, который мы разрабатывали в этой части.
Использование подтверждения адреса электронной почты
Вначале внесем следующие изменения в действие Register контроллера AccountController:
[Route("{area}/{action}")]
[HttpPost]
public async Task<IActionResult> Register(RegisterModel model)
{
var user = model.GetUser();
IdentityResult result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var confirmationLink = Url.Action("ConfirmEmail", "Account", new { token, email = user.Email }, Request.Scheme);
EmailHelper emailHelper = new EmailHelper();
bool emailResponse = emailHelper.SendEmail(user.Email, confirmationLink);
if (emailResponse)
{
user = await _userManager.FindByEmailAsync(user.Email);
//добавляем дату рождения в Claimd пользователя
await _userManager.AddClaimAsync(user, new Claim("Birthday", model.Birthday.ToString("dd.MM.yyyy"), ClaimValueTypes.String));
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "На сервере произошла ошибка. Попробуйте выполнить регистрацию позднее");
return View();
}
}
else
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
return View();
}
}
Рассмотрим, что изменилось в этом методе. После того, как мы создаем объект пользователя, то формируется токен подтверждения адреса электронной почты:
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
Этот токен вставляется в ссылку по которой должен перейти пользователь, чтобы подтвердить почту:
var confirmationLink = Url.Action("ConfirmEmail", "Account", new { token, email = user.Email }, Request.Scheme);
Здесь ConfirmEmail — это действие, которое мы ниже добавим в наш контроллер. После создания ссылки создается новый объект типа EmailHelper у которого выполняется метод SendEmail:
EmailHelper emailHelper = new EmailHelper(); bool emailResponse = emailHelper.SendEmail(user.Email, confirmationLink);
EmailHelper — это нестандартный класс, который необходимо создать, например, в папке Models. Код класса следующий:
public class EmailHelper
{
public bool SendEmail(string userEmail, string confirmationLink)
{
MailMessage mailMessage = new MailMessage
{
From = new MailAddress("ваш_адрес_email_с_которого_будет_отправляться_письмо_пользователю")
};
mailMessage.To.Add(new MailAddress(userEmail));
mailMessage.Subject = "Тема_письма";
mailMessage.IsBodyHtml = true;
mailMessage.Body = confirmationLink;
SmtpClient client = new SmtpClient
{
DeliveryMethod = SmtpDeliveryMethod.Network,
Credentials = new System.Net.NetworkCredential("логин_от_почты", "пароль_от_почты"),
Host = "smtp.yandex.ru",
Port = 25,
EnableSsl = true
};
try
{
client.Send(mailMessage);
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return false;
}
}
Здесь создается smtp-клиент для отправки почты на адрес пользователя. В настройках клиента оставлены настройки smtp-сервера для Яндекса с которыми мне удалось отправить письмо себе на почту. После того, как письмо со ссылкой для подтверждения адреса электронной почты отправлено, для пользователя создается необходимое для аутентификации утверждение (с датой рождения) и пользователь переходит на главную страницу приложения.
Теперь создадим действие контроллера, выполнение которого будет подтверждать адрес электронной почты:
[Route("{area}/{action}")]
public async Task<IActionResult> ConfirmEmail(string token, string email)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
return View("Error");
var result = await _userManager.ConfirmEmailAsync(user, token);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
здесь значения token и email получаются из параметров запроса и используются в методе:
var result = await _userManager.ConfirmEmailAsync(user, token);
для подтверждения адреса электронной почты пользователя. Если адрес успешно подтвержден, то загружается представление с именем ConfirmEmail, иначе — с именем Error. Эти представления необходимо разместить в папке Areas/Account/Views/Account.
Код этих представлений очень простой:
ConfirmEmail.cshtml
@{
ViewData["Title"] = "ConfirmEmail";
}
<h1>Подтверждение Email</h1>
<p>Спасибо за подтверждение. Теперь вы можете войти в свой аккаунт.</p>
Error.cshtml
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Ошибка</h1>
<h2 class="text-danger">
Ошибка выполнения запроса. Возможно Ваш email ещё не подтвержден.
</h2>
Теперь осталось немного изменить действие, выполняемой при входе пользователя в приложение — это действие SignIn в контроллере AccountController:
[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("/");
}
else
{
ModelState.AddModelError("", "Неверный логин или пароль");
}
return View("Login");
}
Здесь, перед тем как осуществить вход с логином/паролем проводится проверка подтверждения пользователем адреса электронной почты:
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");
}
Если поста не подтверждена, то пользователю возвращается соответствующая ошибка. Если почта подтверждена, то осуществляется вход с логином и паролем пользователя.
Проверка подтверждения адреса электронной почты
Создадим нового пользователя с реальным адресом электронной почты:
Через несколько секунд на адрес электронной почты, указанной при регистрации придет письмо:
После перехода по ссылки из письма мы получим следующее сообщение:

и сможем аутентифицировать пользователя
Сервис отправки почты
Теперь, когда мы убедились, что письмо с подтверждением почты отправляется и само подтверждение адреса электронной почты работает, можно немного улучшить наше приложение. Дело в том, что на данный момент мы жестко «зашиваем» в код программы конфиденциальные данные, такие как логин и пароль от почты отправителя. Так в рабочих проектах или проектах, код которых публикуется на сторонних ресурсах, делать, конечно, не стоит. Поэтому, немного улучшим наше приложение и сделаем класс EmailHelper сервисом, который будет получать настройки из секретов пользователя.
Во-первых, создадим класс в котором будем хранить настройки smtp-клиента:
namespace AspMvcAuth.Models
{
public class EmailHelperOptions
{
public string Subject { get; set; }
public string From { get; set; }
public string Login { get; set; }
public string Password { get; set; }
public string Host { get; set; }
public int Port { get; set; }
public bool EnableSSL { get; set; }
}
}
во-вторых, перепишем класс EmailHelper следующим образом:
public class EmailHelper
{
public EmailHelperOptions Options { get; }
public EmailHelper(EmailHelperOptions options)
{
Options = options;
}
public bool SendEmail(string userEmail, string confirmationLink)
{
MailMessage mailMessage = new()
{
From = new MailAddress(Options.From)
};
mailMessage.To.Add(new MailAddress(userEmail));
mailMessage.Subject = Options.Subject;
mailMessage.IsBodyHtml = true;
mailMessage.Body = confirmationLink;
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 (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return false;
}
}
Во-первых, теперь у класса определен конструктор в который передаются настройки smtp-клиента:
public EmailHelperOptions Options { get; }
public EmailHelper(EmailHelperOptions options)
{
Options = options;
}
Во-вторых, из метода SendEmail() убраны все конфиденциальные данные — они берутся из настроек. Для удобства, добавим метод расширения для регистрации нового сервиса:
public static class EmailHelperExtensions
{
public static void AddEmailHelper(this IServiceCollection services, EmailHelperOptions options)
{
services.AddTransient((service)=>new EmailHelper(options));
}
}
Теперь создадим в файле секретов следующие настройки smtp-клиента:
{
"EmailSender": {
"Subject": "Email Confirmation",
"From": "адрес_с_которого_отправляется_письмо",
"Login": "логин_почты",
"Password": "пароль_почты",
"Host": "smtp.yandex.ru",
"Port": 25,
"EnableSSL": true
},
"ConnectionStrings": {
"DefaultConnection": "DataSource=Identity.sqlite"
}
}
Все настройки smtp-клинта находятся в секции EmailSender. Теперь изменим класс Program, добавив новый сервис:
var emailOptions = builder.Configuration.GetSection("EmailSender").Get<EmailHelperOptions>() ?? throw new InvalidOperationException("Email Sender options not found."); ;
builder.Services.AddEmailHelper(emailOptions);
и, наконец, запросим новый сервис в контроллере AccountController:
private readonly EmailHelper _emailHelper;
public AccountController(UserManager<ApplicationUser> manager, SignInManager<ApplicationUser> signInManager, EmailHelper emailHelper)
{
_userManager = manager;
_signInManager = signInManager;
_emailHelper = emailHelper;
}
а в методе Register() используем этот сервис (приведу только часть измененного кода метода):
//EmailHelper emailHelper = new(); bool emailResponse = _emailHelper.SendEmail(user.Email, confirmationLink);
На этом всё — теперь для отправки письма для подтверждения электронной почты будет использоваться сервис, настройки для которого будут храниться в секретах пользователя.
Скачать код проекта из GithubИтого
В этой части мы научились использовать ещё один полезный механизм ASP.NET Core Identity — подтверждение адреса электронной почты нового пользователя. Подтверждение адреса используется для того, чтобы, во-первых, убедиться, что указанный адрес гарантированно доступен для отправки сообщений, а, во-вторых, в дальнейшем, такое подтверждение будет нам необходимо для выполнения двухфакторной аутентификации, которую мы рассмотрим в следующей части.



