Содержание
Подтверждение адреса электронной почты позволяет предположить, что с высокой долей вероятности, зарегистрированный пользователь является реальным и указанный адрес электронной почты рабочий. В этой части мы рассмотрим процесс подтверждения адреса электронной почты для новых пользователей.
Как включить подтверждение адреса электронной почты в 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 — подтверждение адреса электронной почты нового пользователя. Подтверждение адреса используется для того, чтобы, во-первых, убедиться, что указанный адрес гарантированно доступен для отправки сообщений, а, во-вторых, в дальнейшем, такое подтверждение будет нам необходимо для выполнения двухфакторной аутентификации, которую мы рассмотрим в следующей части.