Содержание
В предыдущей части мы разработали небольшое приложение, позволяющее аутентифицировать пользователя с использованием токенов JWT. Также мы протестировали работу приложения с использованием сервиса ReqBin и убедились, что токен работает — получили доступ к конечной точке, требующей авторизации пользователя. Однако, при разработке приложений (если это не обычный API для получения доступа к данным от сторонних клиентов) необходимо также предусмотреть и авторизацию пользователей с использованием полученного JWT-токена. И сегодня мы рассмотрим один из возможных вариантов того как происходит авторизация клиента с помощью JWT в ASP.NET Core
Как известно, любую задачу в программировании можно решить несколькими способами. Поле получения JWT-токена мы можем поступать с ним как угодно — например, написать клиент на JavaScript для передачи токена в заголовке Authorization. Также, мы можем воспользоваться возможностями ASP.NET Core и, например, сохранять полученный токен в cookies или использовать сессии. Рассмотрим один из вариантов работы с токеном через cookies.
На данный момент, в нашем приложении определена конечная точка для входа пользователя в систему:
app.MapGet("/login", (string? login, string? password, HttpContext context) =>
{
if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password))
return Results.BadRequest(new { Error = "Не задан логин или пароль" });
var user = UserDatabase.FirstOrDefault(u => (u.Login == login) && (u.Password == password));
if (user == null)
return Results.BadRequest(new { Error = "Не верно введен логин или пароль" });
JwtSecurityToken token = new(
issuer: authOptions?.Issuer,
audience: authOptions?.Audience,
expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(authOptions.TokenLifetime)),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
string tokenString = handler.WriteToken(token);
return Results.Ok(new { token = tokenString, user = login });
});
Здесь мы можем сделать следующее — передать cookie с токеном клиенту и, затем, получать значение токена при каждом запросе. Допишем код конечной точки следующим образом:
app.MapGet("/login", (string? login, string? password, HttpContext context) =>
{
//здесь код формирования токена
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
string tokenString = handler.WriteToken(token);
context.Response.Cookies.Append("token", tokenString);//отпраляем клиенту куку с токеном
return Results.Ok(new { token = tokenString, user = login });
});
Теперь, как только пользователь введет верный логин и пароль, он получит токен в виде куки.
Авторизация клиента с помощью JWT
Чтобы клиент смог авторизоваться по этому токену необходимо каким-либо образом значение куки передать в заголовок авторизации. Сделать это можно, например, написав свой компонент middleware, который будет встраиваться в конвейер перед всеми конечными точками для которых необходима авторизация.
Компонент будет выглядеть достаточно просто:
app.Use(async (context, next) =>
{
if (context.Request.Cookies.TryGetValue("token", out string? token))
context.Request.Headers.Authorization = $"Bearer {token}";
await next();
});
по сути, всё, что мы здесь делаем — это пробуем получить куку с именем «token» и, если таковая находится, то её значение передается в заголовок авторизации:
context.Request.Headers.Authorization = $"Bearer {token}";
Встраиваем этот middleware в начало конвейера запросов (сразу после строки var app = builder.Build();):
var app = builder.Build();
app.Use(async (context, next) =>
{
if (context.Request.Cookies.TryGetValue("token", out string? token))
context.Request.Headers.Authorization = $"Bearer {token}";
await next();
});
Протестируем наше приложение
получение токена по логину и паролю
получаем доступ к ресурсу для которого требуется авторизация:
так как мы ничего не меняли в приложении с прошлого раза, то в ответ нам, как и предполагалось, вернулись настройки генерации токена, что говорит нам о том, что наш способ авторизации клиента работает. Кроме этого, если подождать пару минут и повторить запрос, то получим ошибку доступа так как токен «живет» две минуты
Исходный код проекта
Ниже представлен весь исходный код проекта на данный момент
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
namespace AspAuth
{
public class AuthOptions
{
public string Issuer { get; set; } //издатель токена
public string Audience { get; set; } //потребитель токена
public string SecretKey { get; set; } //секретный ключ для подписи
public bool ValidateAudience { get; set; } //проверять потребителя
public bool ValidateIssuer { get; set; } //проверять издателя
public bool ValidateLifetime { get; set; } //проверять время жизни токена
public bool ValidateIssuerSigningKey { get; set; } //проверять подпись
public int TokenLifetime { get; set; } //время жизни токена в минутах
}
public class User
{
public string Login { get; set; }
public string Password { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
//условная база данных с учетными записями пользователей
List<User> UserDatabase = new()
{
new User()
{
Login = "user",
Password ="12345"
},
new User()
{
Login = "admin",
Password = "root"
},
};
var builder = WebApplication.CreateBuilder(args);
var authOptions = builder.Configuration.GetSection("Jwt").Get<AuthOptions>();
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authOptions.SecretKey));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = authOptions.ValidateIssuer,
ValidateAudience = authOptions.ValidateAudience,
ValidateLifetime = authOptions.ValidateLifetime,
ValidateIssuerSigningKey = authOptions.ValidateIssuerSigningKey,
ValidIssuer = authOptions.Issuer,
ValidAudience = authOptions.Audience,
IssuerSigningKey = key
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.Use(async (context, next) =>
{
if (context.Request.Cookies.TryGetValue("token", out string? token))
context.Request.Headers.Authorization = $"Bearer {token}";
await next();
});
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", () => "Hello World!");
app.MapGet("/auth", [Authorize] () =>
{
return Results.Ok(authOptions);
});
app.MapGet("/login", (string? login, string? password, HttpContext context) =>
{
if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password))
return Results.BadRequest(new { Error = "Не задан логин или пароль" });
var user = UserDatabase.FirstOrDefault(u => (u.Login == login) && (u.Password == password));
if (user == null)
return Results.BadRequest(new { Error = "Не верно введен логин или пароль" });
JwtSecurityToken token = new(
issuer: authOptions?.Issuer,
audience: authOptions?.Audience,
expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(authOptions.TokenLifetime)),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
string tokenString = handler.WriteToken(token);
context.Response.Cookies.Append("token", tokenString);//отпраляем клиенту куку с токеном
return Results.Ok(new { token = tokenString, user = login });
});
app.Run();
}
}
}
Итого
Сегодня мы рассмотрели один из возможных вариантов авторизации клиента по JWT-токену. Для сохранения и передачи токена на сервер мы использовали Cookies, а для формирования заголовка авторизации — написали свой компонент middleware, который встроили в самое начало конвейера запросов.

