[ Полезный рекламный блок ]
Попробуйте свои силы в игре, где ваши навыки программирования на C# станут решающим фактором. Переходите по ссылке 🔰.
Сьогодні ми розробимо додаток типу Asp.Net Core MVC Identity, з використанням Google Авторизації.
Річ у тім, що в даний час у багатьох людей вже є облікові записи, створені в сторонніх додатках, таких як Microsoft, Google, Facebook, Twitter і т. д. Ми довіряємо цим стороннім додаткам і використовуємо їх для автентифікації та ідентифікації користувача. Тому такі сторонні додатки називаються довіреними постачальниками ідентифікаційних даних.
Google Авторизация с Identity
Якщо користувач захоче увійти в наш застосунок, використовуючи свій акаунт Google, він натисне на створену нами кнопку Google:
Введе свої дані:
Після чого, буде успішно авторизований на вашому сайті!
Створення облікового запису Google OAuth
Google Авторизація з Identity вимагає створення облікового запису в компанії Google. Нам потрібні ClientId і Client Secret у додатку для автентифікації Google. Перейдіть на наступний сайт і увійдіть у систему з обліковими даними Google:
https://console.cloud.google.com/apis/
Щойно ви увійшли в систему, першим кроком буде створення нового проєкту, якщо у вас його ще немає. Наразі у мене немає жодних проєктів, тому давайте створимо новий проєкт, натиснувши на Select a project. Якщо у вас уже є проєкти, то зверху буде виведено назву поточного проєкту, за яким необхідно натиснути:
Тепер дайте осмислене ім’я своєму проєкту. Я назвав свій проект Google Auth STS. STS означає Security Token Service, тому що ми використовуємо цей проект для забезпечення служби ідентифікації нашого додатка .NET Core:
Натиснемо CREATE, щоб створити цей новий проєкт.
Після створення нового проєкту наступним кроком буде включення API класу Google.
Для цього натисніть на навігаційне меню бібліотеки зліва, але якщо з якоїсь причини ви не бачите цього навігаційного меню, натисніть на логотип Google APIs, і ви побачите його. У самому меню виберіть пункт – Library:
У вас відкриється сторінка пошуку, знайдіть тут Google+ API і натисніть на нього:
Тепер на цьому екрані єдиним обов’язковим полем є ім’я застосунку, це ім’я буде показано користувачеві на екрані згоди, наразі ми створюємо ці облікові дані OAuth для нашого застосунку .NET Core.
Тип користувача – External:
Я вказав нижче назву додатка, пошту підтримки користувачів і адресу електронної пошти розробника:
Тепер прокрутіть сторінку донизу і натисніть кнопку – Зберегти та продовжити.
Після цього потрібно натиснути на навігаційну панель облікових даних, а потім вибрати пункт: CREATE CREDENTIALS – OAuth client ID.
Тепер на цьому екрані виберіть Web Application, тому що це тип нашого клієнтського додатка, далі вкажіть ім’я для клієнта. Я вказав ім’я Identity client. Далі Authorised JavaScript origins, є URI адресою, на якій розміщено клієнтський додаток:
Наразі наш проєкт все ще працює на локальній машині. Щоб отримати URI, за яким наш проєкт розміщений на локальній машині, у структурі проєкту Solution Explorer – Properties – launchSettings.json, у цьому файлі буде локальна url-адреса проєкту:
1 2 3 4 5 6 7 8 9 10 |
"profiles": { "GoogleAuth": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:7172;http://localhost:5172", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, |
Нарешті, ви маєте налаштувати авторизовані URI перенаправлення. Це URI, на який користувач буде перенаправлений після аутентифікації в Google. Вкажіть там таку адресу:
https://localhost:7172/signin-google
Сторінка з назвою signin-google, обов’язкова!
Натиснемо кнопку – Create. Отже, тепер ви отримуєте Client_Id і Client_Secret:
Нам потрібні обидва ці ідентифікатори для інтеграції автентифікації google в наш проєкт, завдяки їм Google Авторизація з Identity працюватиме.
Створення проекту
Я очікую від вас знання концепцій об’єктно-орієнтованого програмування на C#. Я припускаю, що ви також знаєте концепції .NET Core, особливо патерн MVC.
Відкрийте Visual Studio 2022.
Створіть проєкт “Asp.Net Core Web Application(.Net Core)” із шаблоном MVC Template. У графі “Authentication type” залишаємо значення None.
Отже, тепер вам потрібно налаштувати автентифікацію Google для нашого додатка .NET Core, ми зробимо це в нашому початковому класі Program:
1 2 3 4 5 6 |
builder.Services.AddAuthentication() .AddGoogle(options => { options.ClientId = "898063592413-mobd4vo6t6jedcpi2d6lfr9s2958go82.apps.googleusercontent.com"; options.ClientSecret = "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"; }); |
Якщо у вас уже додається цей сервіс, просто розширте його налаштування, якщо не додається, додайте як у прикладі. Код, необхідний для аутентифікації Google, включно з цим методом AddGoogle(), присутній у пакеті:
Microsoft.AspNetCore.Authentication.Google
Не забудьте додати його в проєкт.
Тепер займемося налаштуванням Identity з нуля. Спочатку додамо в папку Models новий клас User:
1 2 3 4 |
public class User : IdentityUser { public int Year { get; set; } } |
Клас User представляє користувача і успадковується від класу IdentityUser, переймаючи всі його властивості. Крім того, для прикладу тут додано властивість Year, яка представлятиме рік народження користувача. За бажання можна визначити будь-які інші властивості.
Для взаємодії з MS SQL Server через ASP.NET Core Identity додамо в проєкт через Nuget пакети:
1 2 3 |
Microsoft.AspNetCore.Identity.EntityFrameworkCore Microsoft.EntityFrameworkCore.SqlServer |
Далі додамо в папку Data клас контексту даних ApplicationContext. Оскільки ми використовуємо Identity, то клас контексту даних буде успадковуватися не від DbContext, а від IdentityDbContext:
1 2 3 4 5 6 7 8 |
public class ApplicationContext : IdentityDbContext<User> { public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options) { Database.EnsureCreated(); } } |
У нас є контекст і моделі, і тепер нам необхідна база даних, яка буде зберігати всі дані. Спочатку визначимо у файлі appsettings.json рядок підключення:
1 2 3 |
"ConnectionStrings": { "DefaultConnection": "Server=(localdb)\mssqllocaldb;Database=usersstoredb;Trusted_Connection=True;" }, |
Далі нам треба змінити клас Program, щоб застосувати всі необхідні сервіси для роботи з Identity і базою даних:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
using GoogleAuth.Data; using GoogleAuth.Models; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); builder.Services.AddAuthentication() .AddGoogle(options => { options.ClientId = "898063592413-mobd4vo6t6jedcpi2d6lfr9s2958go82.apps.googleusercontent.com"; options.ClientSecret = "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"; }); IConfigurationRoot _confString = new ConfigurationBuilder(). SetBasePath(AppDomain.CurrentDomain.BaseDirectory).AddJsonFile("appsettings.json").Build(); builder.Services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(_confString.GetConnectionString("DefaultConnection"))); builder.Services.AddIdentity<User, IdentityRole>() .AddEntityFrameworkStores<ApplicationContext>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); //подключение аутентификации app.UseAuthorization(); //подключение авторизации app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); |
Метод AddIdentity() дає змогу встановити деяку початкову конфігурацію. Тут ми вказуємо тип користувача і тип ролі, які будуть використовуватися системою Identity. Як тип користувача виступає створений нами вище клас User, а як тип ролі взято стандартний клас IdentityRole, який знаходиться в просторі імен Microsoft.AspNetCore.Identity.EntityFrameworkCore.
Метод AddEntityFrameworkStores() встановлює тип сховища, яке буде застосовуватися в Identity для зберігання даних. Як тип сховища тут вказується клас контексту даних.
Потім, щоб використовувати Identity, у методі Configure() встановлюється компонент middeware – UseAuthentication. Причому це middleware викликається перед app.UseEndpoints(), у такий спосіб гарантуючи, що на час звернення до системи маршрутизації, контролерів та їхніх методів, куки належно опрацьовані та встановлені.
Тепер система Identity під’єднана до проєкту, і ми можемо з нею працювати.
Авторизація користувачів в Identity
Продовжимо роботу з проєктом і додамо в нього функціонал авторизації користувачів. Користувачі в додатку представлені класом User, який успадковує безліч властивостей. Однак нам необов’язково всіх їх встановлювати. Достатньо встановити ключові властивості на кшталт логіна і пароля.
Для цього краще скористатися допоміжною моделлю, яка встановить усі необхідні властивості. Отже, додамо в проєкт нову папку, яку назвемо ViewModels. Потім у цій папці визначимо новий клас LoginViewModel, який буде представляти користувача, що авторизує:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class LoginViewModel { [Required] [Display(Name = "Email")] public string? Email { get; set; } [Required] [DataType(DataType.Password)] [Display(Name = "Пароль")] public string? Password { get; set; } [Display(Name = "Запомнить?")] public bool RememberMe { get; set; } public string? ReturnUrl { get; set; } public IList<AuthenticationScheme> ExternalLogins { get; set; } } |
Увімкніть властивості ReturnUrl і ExternalLogins.
ReturnUrl – це URL, до якого користувач намагався отримати доступ до аутентифікації. Ми зберігаємо і передаємо його між запитами за допомогою властивості ReturnUrl, щоб користувач міг бути перенаправлений на цей URL після успішної аутентифікації.
Так само необхідно показати всі зовнішні логіни (Facebook, Google тощо) на сторінці входу. Для цього додаємо властивість ExternalLogins у LoginViewModel.
Для роботи з обліковими записами користувачів додамо в папку Controllers новий контролер AccountController і визначимо в ньому метод для авторизації користувачів через звичайний вхід:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
public class AccountController : Controller { private readonly UserManager<User> _userManager; private readonly SignInManager<User> _signInManager; public AccountController(UserManager<User> userManager, SignInManager<User> signInManager) { _userManager = userManager; _signInManager = signInManager; } [HttpGet] [AllowAnonymous] public async Task<IActionResult> Login(string returnUrl) { LoginViewModel model = new LoginViewModel { ReturnUrl = returnUrl, ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList() }; return View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginViewModel model) { if (ModelState.IsValid) { var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, false); if (result.Succeeded) { // проверяем, принадлежит ли URL приложению if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl)) { return Redirect(model.ReturnUrl); } else { return RedirectToAction("Index", "Home"); } } else { ModelState.AddModelError("", "Неправильный логин и (или) пароль"); } } return View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Logout() { // удаляем аутентификационные куки await _signInManager.SignOutAsync(); return RedirectToAction("Index", "Home"); } } |
Для подань цього контролера в каталозі Views визначимо підкаталог Account, в який додамо нове подання Login.cshtml. Це подання слугуватиме для реєстрації користувача:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
@model GoogleAuth.ViewModels.LoginViewModel <div class="row"> <div class="col-md-4"> <h2>Авторизация</h2> <form method="post" asp-controller="Account" asp-action="Login" asp-route-returnUrl="@Model.ReturnUrl"> <div asp-validation-summary="ModelOnly"></div> <div class="form-group"> <label asp-for="Email" class="control-label"></label> <input asp-for="Email" class="form-control" /> <span asp-validation-for="Email" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Password" class="control-label"></label> <input asp-for="Password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <div> <label asp-for="RememberMe" class="control-label"></label><br /> <input asp-for="RememberMe" class="form-check" /> </div> <div class="form-group"> <br /> <input type="submit" value="Войти" class="btn btn-primary" /> </div> </form> </div> <div class="col-md-4"> <h2>Авторизация через Google</h2> <hr /> @{ if (Model.ExternalLogins.Count == 0) { <div>No external logins configured</div> } else { <form method="post" asp-action="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl"> <div> @foreach (var provider in Model.ExternalLogins) { <button type="submit" class="btn btn-primary" style="width:auto" name="provider" value="@provider.Name" title="Login using your @provider.DisplayName account"> @provider.DisplayName </button> } </div> </form> } } </div> </div> |
Запустимо додаток, під час звернення за адресою:
https://localhost:7243/account/login
ми побачимо наступну сторінку:
Якщо ви натиснете на кнопку Google, ця кнопка є кнопкою типу Submit у коді подання, тоді ви виявите помилку. Тому що ви ще не створили дію ExternalLogin у контролері – AccountController. Створимо її:
1 2 3 4 5 6 7 8 9 10 11 12 |
ExternalLogin в контроллере - AccountController. Создадим его: [AllowAnonymous] [HttpPost] public IActionResult ExternalLogin(string provider, string returnUrl) { var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); return new ChallengeResult(provider, properties); } |
Тепер запустимо додаток знову, і натиснемо на кнопку Google:
Ви побачите назву вашого проєкту, у моєму випадку – Auth, але сам процес авторизації ще не готовий. У контролер AccountController додамо дію ExternalLoginCallback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
[AllowAnonymous] public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null) { returnUrl = returnUrl ?? Url.Content("~/"); LoginViewModel loginViewModel = new LoginViewModel { ReturnUrl = returnUrl, ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList() }; if (remoteError != null) { ModelState .AddModelError(string.Empty, $"Error from external provider: {remoteError}"); return View("Login", loginViewModel); } // Get the login information about the user from the external login provider var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) { ModelState .AddModelError(string.Empty, "Error loading external login information."); return View("Login", loginViewModel); } // If the user already has a login (i.e if there is a record in AspNetUserLogins // table) then sign-in the user with this external login provider var signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); if (signInResult.Succeeded) { return LocalRedirect(returnUrl); } // If there is no record in AspNetUserLogins table, the user may not have // a local account else { // Get the email claim value var email = info.Principal.FindFirstValue(ClaimTypes.Email); if (email != null) { // Create a new user without password if we do not have a user already var user = await _userManager.FindByEmailAsync(email); if (user == null) { user = new User { UserName = info.Principal.FindFirstValue(ClaimTypes.Email), Email = info.Principal.FindFirstValue(ClaimTypes.Email) }; await _userManager.CreateAsync(user); } // Add a login (i.e insert a row for the user in AspNetUserLogins table) await _userManager.AddLoginAsync(user, info); await _signInManager.SignInAsync(user, isPersistent: false); return LocalRedirect(returnUrl); } ViewBag.ErrorTitle = $"Email claim not received from: {info.LoginProvider}"; ViewBag.ErrorMessage = "Please contact support on Pragim@PragimTech.com"; return View("Error"); } } |
Для перевірки логіна і зручності роботи з додатком, визначимо в поданні Layout.cshtml виведення імені користувача і посилання на вхід, якщо користувач не авторизований і вихід, якщо він авторизований:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<ul class="navbar-nav"> @if (User.Identity.IsAuthenticated) { <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="" asp-action="">@User.Identity.Name</a> </li> <li class="nav-item"> <form method="post" asp-controller="Account" asp-action="Logout"> @*Добавляем стили, что бы кнопка выглядела как ссылка*@ <style> .mybtn { background: none !important; border: none; cursor: pointer; } </style> @*Применяем класс стилей для кнопки*@ <input class="nav-link text-dark mybtn" type="submit" value="Выход" /> </form> </li> } else { <li class="nav-item"> <a class="nav-link text-dark" asp-controller="Account" asp-action="Login">Вход</a> </li> } </ul> |
Якщо ви запустите додаток і натиснете кнопку Google, потім виберіть обліковий запис Gmail, потім вкажіть свій пароль для входу в Gmail, то ви побачите результат, як і очікувалося:
Ви успішно увійшли у свій акаунт, використовуючи зовнішнього постачальника послуг входу.
Якщо ви подивитеся на таблицю AspNetUsers, то виявите цікаву річ. У таблиці AspNetUsers у нас є новий рядок, подивіться на колонку PasswordHash, вона дорівнює NULL. Насправді, нам не потрібен пароль для цього користувача. Нам більше не потрібно його підтримувати, тому що цей користувач переходить до зовнішнього провайдера входу в акаунт Google.
Сподіваюся, вам сподобалася ця стаття. Ви можете завантажити вихідний код у моєму репозиторії — Github.
Поділіться вашим досвідом у коментарях, як ви виконуєте Google Авторизацію з Identity, у ваших додатках?
Ви хочете навчитися писати код мовою програмування C#?
Створювати різні інформаційні системи, що складаються з сайтів, мобільних клієнтів, десктопних додатків, телеграм-ботів тощо.
Переходьте до нас на сторінку Dijix і ознайомтеся з умовами навчання, ми спеціалізуємося тільки на індивідуальних заняттях, як для початківців, так і для просунутих програмістів. Ви можете взяти як одне заняття для опрацювання питання, що вас цікавить, так і кілька, для більш щільної роботи. Завдяки особистому кабінету, кожен студент підвищить якість свого навчання, у вашому розпорядженні:
- Доступ до пройденого матеріалу
- Тематичні статті
- Бібліотека книг
- Онлайн тестування
- Спілкування в закритих групах
await _signInManager.SignOutAsync(); насправді не видаляє куки
SignOutAsync() – це асинхронний метод, який асинхронно відключає поточного користувача із системи. Цей метод анулює всі ідентифікаційні відомості, пов’язані з користувачем у поточному контексті.
У процесі виконання цього методу, користувацьке ідентифікаційне кукі (або інші механізми аутентифікації) видаляються з поточного HTTP-контексту. Таким чином, подальші запити від цього користувача не вважатимуться аутентифікованими.