Аутентификация и авторизация в ASP.NET Core. Аутентификация с помощью cookies

До этого момента мы разбирались с аутентификацией и авторизацией пользователей в ASP.NET Core с использованием JWT-токенов. При этом, не менее популярным вариантом аутентификации пользователей не только в ASP.NET Core, но и, в принципе, в веб-приложениях, является аутентификация с помощью cookies (куки). Рассмотрим этот вариант аутентификации, разработав небольшое приложение ASP.NET

Подключение и настройка необходимых сервисов и компонентов middleware

Создадим новое приложение ASP.NET Core с шаблоном Empty и сразу настроим необходимые элементы аутентификации и авторизации:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;

namespace AspCookieAuth
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            //подключаем аутентификацию со схемой Cookies
            builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options => 
                { 
                    options.LoginPath = "/login"; 
                    options.Cookie.Name = "authCookie";
                });
            //подключаем серсив авторизации
            builder.Services.AddAuthorization();

            var app = builder.Build();

            //подключаем необходимые компоненты middleware
            app.UseAuthentication();
            app.UseAuthorization(); 


            app.MapGet("/", [Authorize]() => "Hello World!");

            app.Run();
        }
    }
}

при подключении аутентификации мы указали схему Cookies, а также вызвали метод AddCookie в котором настроили аутентификацию, а именно:

options.LoginPath = "/login";

указывает путь по которому будет перенаправлен пользователь при попытку получить доступ к защищенному ресурсу:

options.Cookie.Name = "authCookie";

имя аутентификационной куки.

У конечной точки «/» мы указали атрибут Authorize. Теперь, если мы запустим приложение, то увидим вот такое сообщение об ошибке:

Так как страницы «/login» у нас ещё нет. Создадим её.

Создание необходимых конечных точек

app.MapGet("/login", async (HttpContext context) =>
{
    context.Response.ContentType = "text/html; charset=utf-8";
    // html-форма для ввода логина/пароля
    await context.Response.SendFileAsync(@"wwwroot\login.html");
});

app.MapPost("/login", async (string? returnUrl, HttpContext context) =>
{
    // получаем из формы логин и пароль
    var form = context.Request.Form;
    // если email и/или пароль не установлены, посылаем статусный код ошибки 400
    if (!form.ContainsKey("login") || !form.ContainsKey("password"))
        return Results.BadRequest("Логин и/или пароль не установлены");

    string login = form["login"];
    string password = form["password"];

    // находим пользователя 
    User? user = users.FirstOrDefault(p => p.Login == login && p.Password == password);
    // если пользователь не найден, отправляем статусный код 401
    if (user is null) 
        return Results.Unauthorized();

    var claims = new List<Claim> 
    { 
        new Claim(ClaimTypes.Name, user.Login) 
    };
    
    // создаем объект ClaimsIdentity
    ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    
    // установка аутентификационных куки
    await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
    
    return Results.Redirect(returnUrl ?? "/");
});

Если пользователь выполняет GET-запрос на адрес "/login", то срабатывает обработчик, который отправляет пользователю содержимое файла "login.html", который был создан в папке wwwroot:

и имеет следующее содержимое:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Вход в систему</title>
</head>
<body>
    <form method='post'>
        <p>
            <label>Login</label><br />
            <input name='login' />
        </p>
        <p>
            <label>Password</label><br />
            <input type='password' name='password' />
        </p>
        <input type='submit' value='Login' />
    </form>
</body>
</html>

Это обычная web-форма для ввода логина и пароля пользователя. При нажатии на кнопку «Login» выполняется POST-запрос, то есть срабатывает следующая конечная точка:

app.MapPost("/login", async (string? returnUrl, HttpContext context) =>
{
    // получаем из формы логин и пароль
    var form = context.Request.Form;
    if (!form.ContainsKey("login") || !form.ContainsKey("password"))
        return Results.BadRequest("Логин и/или пароль не установлены");

    string login = form["login"];
    string password = form["password"];

    // находим пользователя 
    User? user = users.FirstOrDefault(p => p.Login == login && p.Password == password);
    // если пользователь не найден, отправляем статусный код 401
    if (user is null) 
        return Results.Unauthorized();

    var claims = new List<Claim> 
    { 
        new Claim(ClaimTypes.Name, user.Login) 
    };
    
    // создаем объект ClaimsIdentity
    ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    
    // установка аутентификационных куки
    await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
    
    return Results.Redirect(returnUrl ?? "/");
});

здесь мы выполняем следующие действия:

Получаем доступ к содержимому формы:

// получаем из формы логин и пароль
var form = context.Request.Form;
if (!form.ContainsKey("login") || !form.ContainsKey("password"))
    return Results.BadRequest("Логин и/или пароль не установлены");

string login = form["login"];
string password = form["password"];

если в форме нет логина или пароля пользователя, то отправляем пользователю ошибку BadRequest, используя статический класс Results. Если форма заполнена корректно, то считываем логин и пароль в локальные переменные.

Ищем пользователя в списке users.

// находим пользователя 
User? user = users.FirstOrDefault(p => p.Login == login && p.Password == password);
// если пользователь не найден, отправляем статусный код 401
if (user is null) 
    return Results.Unauthorized();

В приложении этот список выглядит следующим образом:

        
public class User
{
    public string Login { get; set; }
    public string Password { get; set; }
}

var users = new List<User>()
{
    new User(){Login = "admin", Password = "root"},
    new User(){Login = "tom", Password = "user"},
};

Далее мы создаем объект ClaimIdentity со списком объектов Claim в который добавляем только имя пользователя

var claims = new List<Claim> 
{ 
    new Claim(ClaimTypes.Name, user.Login) 
};

// создаем объект ClaimsIdentity
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);

с классом ClaimIdentity мы уже знакомились здесь.

Далее мы устанавливаем аутентификационные куки:

await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));

здесь мы используем один из методов расширения объекта HttpContextSignInAsync, который принимает в качестве параметров схему аутентификации и объект типа ClaimsPrincipal.

После установки куки отправляем пользователя снова на главную страницу приложения:

return Results.Redirect(returnUrl ?? "/");

Для выхода из приложения предусмотрим следующую конечную точку

app.MapGet("/logout", async (HttpContext context) =>
{
    await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return Results.Redirect("/login");
});

Посмотрим на работу приложения.

Проверка работы приложения

Запускаем приложение и автоматически переходим на форму аутентификации:

Вводим логин и пароль пользователя и жмем кнопку Login:

В инструментах разработчика в браузере можно убедиться, что кука была установлена:

Переходим по пути «/logout» и убеждаемся, что аутентификационная кука убирается:

Приложение работает так как и задумывалось. Приведем весь исходный код приложения

Исходный код приложения

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;

using System.Security.Claims;

namespace AspCookieAuth
{
    public class User
    {
        public string Login { get; set; }
        public string Password { get; set; }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var users = new List<User>()
            {
                new User(){Login = "admin", Password = "root"},
                new User(){Login = "tom", Password = "user"},
            };


            var builder = WebApplication.CreateBuilder(args);

            //подключаем аутентификацию со схемой Cookies
            builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.LoginPath = "/login";
                    options.Cookie.Name = "authCookie";
                });
            //подключаем серсив авторизации
            builder.Services.AddAuthorization();

            var app = builder.Build();

            //app.UseStaticFiles();

            //подключаем необходимые компоненты middleware
            app.UseAuthentication();
            app.UseAuthorization();


            app.MapGet("/", [Authorize] () => "Hello World!");

            app.MapGet("/login", async (HttpContext context) =>
            {
                context.Response.ContentType = "text/html; charset=utf-8";
                // html-форма для ввода логина/пароля
                await context.Response.SendFileAsync(@"wwwroot\login.html");
            });

            app.MapPost("/login", async (string? returnUrl, HttpContext context) =>
            {
                // получаем из формы логин и пароль
                var form = context.Request.Form;
                if (!form.ContainsKey("login") || !form.ContainsKey("password"))
                    return Results.BadRequest("Логин и/или пароль не установлены");

                string login = form["login"];
                string password = form["password"];

                // находим пользователя 
                User? user = users.FirstOrDefault(p => p.Login == login && p.Password == password);
                // если пользователь не найден, отправляем статусный код 401
                if (user is null) 
                    return Results.Unauthorized();

                var claims = new List<Claim> 
                { 
                    new Claim(ClaimTypes.Name, user.Login) 
                };
                
                // создаем объект ClaimsIdentity
                ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
                
                // установка аутентификационных куки
                await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
                
                return Results.Redirect(returnUrl ?? "/");
            });


            app.MapGet("/logout", async (HttpContext context) =>
            {
                await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                return Results.Redirect("/login");
            });

            app.Run();
        }

        
    }
}

Итого

Аутентификация на основе cookie используется в ASP.NET Core наряду с другими схемами аутентификации и, по сравнению с аутентификацией по JWT-токенам выглядит более простой. Тем не менее такой подход к аутентификации пользователей широко используется в веб-приложениях. Что касается использования авторизации по ролям или с использованием политик, то здесь используются те же механизмы, которые мы рассматривали ранее.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии