Ответ
JWT (JSON Web Token) — стандартный способ передачи claims между сторонами в виде подписанного JSON-объекта. В .NET для работы с JWT используется пакет System.IdentityModel.Tokens.Jwt.
1. Генерация Access Token Токен создается на сервере аутентификации после успешной проверки учетных данных.
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
public string GenerateJwtToken(string userId, string email, List<string> roles)
{
// 1. Подготовка секретного ключа (в продакшене храните в безопасном месте, например, в Azure Key Vault)
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Secret"]));
var signingCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
// 2. Формирование claims (утверждений о пользователе)
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, userId), // Subject (идентификатор пользователя)
new Claim(JwtRegisteredClaimNames.Email, email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // Unique token ID
};
// Добавление ролей как отдельных claims (стандарт для ASP.NET Core)
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
// 3. Создание самого токена
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"], // Кто выдал
audience: _configuration["Jwt:Audience"], // Для кого предназначен
claims: claims,
expires: DateTime.UtcNow.AddMinutes(15), // Короткое время жизни Access Token
signingCredentials: signingCredentials
);
// 4. Кодирование токена в строку
return new JwtSecurityTokenHandler().WriteToken(token);
}
2. Валидация токена в API
В ASP.NET Core валидация настраивается централизованно в Program.cs или Startup.cs.
// Конфигурация аутентификации
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"])
),
ValidateLifetime = true, // Проверять срок действия
ClockSkew = TimeSpan.Zero // Не давать "форы" по времени (опционально)
};
// Для Bearer-токенов из заголовка Authorization этого достаточно.
// Для токенов из кук или query-строки настройте options.Events.
});
// Затем используйте атрибут [Authorize] на контроллерах или методах.
3. Refresh Token Механизм
Access Token короткоживущий, Refresh Token — долгоживущий (дни, недели), хранится безопасно (в HttpOnly куке или БД) и используется для получения новой пары токенов.
// Генерация Refresh Token (случайная строка, сохраненная в БД с привязкой к userId)
public string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
// Эндпоинт для обновления токенов
[HttpPost("refresh-token")]
public async Task<IActionResult> RefreshToken(RefreshTokenRequest request)
{
// 1. Валидируем старый Access Token (даже истекший) чтобы извлечь userId
// 2. Проверяем, что переданный Refresh Token существует и активен для этого userId в БД
// 3. Если все ок, генерируем новую пару Access/Refresh токенов
// 4. Старый Refresh Token инвалидируем (удаляем или помечаем использованным)
// 5. Возвращаем новые токены клиенту
}
4. Критически важные практики безопасности:
- Никогда не храните секретный ключ (Secret) в коде. Используйте менеджеры секретов (Azure Key Vault, HashiCorp Vault) или переменные окружения.
- Используйте HTTPS (TLS) всегда. JWT передается открытым текстом.
- Храните Refresh Tokens безопасно:
HttpOnly,Secure,SameSite=Strictкуки — лучший вариант для веб-приложений. - Реализуйте отзыв токенов (token revocation): Вести черный список отозванных токенов (по
jti) или инвалидировать все токены пользователя при смене пароля. - Ограничивайте claims: Не помещайте в токен чувствительные данные (пароли).
Ответ 18+ 🔞
Слушай, вот тебе про JWT, но без этой заумной сухой документации. Представь, что это такой пропуск в ночной клуб, только для твоего API.
1. Штампуем пропуск (Access Token) После того как пользователь доказал, что он не левый (логин-пароль), надо ему выдать эту бумажку.
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
public string СгенеритьТокен(string userId, string email, List<string> roles)
{
// 1. Секретный ключ. Это как печать, которой штампуем.
// НАХУЙ НЕ ХРАНИ ЭТО В КОДЕ! Вынеси в конфиг, в переменные окружения, в сейф — куда угодно, только не тут.
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Secret"]));
var signingCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
// 2. Что пишем в пропуске (claims). Типа "Имя: Вася, Должность: Гуру".
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, userId), // Кто владелец
new Claim(JwtRegisteredClaimNames.Email, email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // Уникальный номер пропуска, чтобы потом отозвать, если что
};
// Добавляем роли отдельными строчками
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
// 3. Собираем сам пропуск
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"], // Кто выдал (название твоего приложения)
audience: _configuration["Jwt:Audience"], // Для кого (обычно тоже твоё API)
claims: claims,
expires: DateTime.UtcNow.AddMinutes(15), // Живёт 15 минут! Коротко — чтобы, если украли, недолго радовались.
signingCredentials: signingCredentials // Подписываем нашей печатью
);
// 4. Превращаем в строку, которую можно в заголовок воткнуть
return new JwtSecurityTokenHandler().WriteToken(token);
}
2. Проверка пропуска на входе (в API) В ASP.NET Core это настраивается один раз, а потом работает автоматом. Ставишь швейцара.
// В Program.cs или Startup.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, // Проверяем, что мы сами выдали
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidateAudience = true, // Проверяем, что он для нашего API
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidateIssuerSigningKey = true, // Самое главное: проверяем подпись (печать)
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"])
),
ValidateLifetime = true, // Смотрим, не просрочен ли
ClockSkew = TimeSpan.Zero // Без форы. Просрочился — нахуй, в баню.
};
// Всё, теперь любой запрос с заголовком `Authorization: Bearer <токен>` будет проверяться.
});
// А на контроллер или метод вешаешь [Authorize] — и туда только со valid пропуском.
3. Механизм Refresh Token'ов (чтобы не логиниться каждые 15 минут) Access Token — это временный пропуск. Refresh Token — это годовая подписка, по которой тебе выдают новый временный пропуск. Хранить её надо как зеницу ока.
// Генерация Refresh Token'а — просто случайная длинная строка
public string СгенеритьРефрешТокен()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
// И ЭТУ СТРОКУ НАДО СОХРАНИТЬ В БАЗУ, привязав к userId! Иначе как проверишь потом?
}
// Эндпоинт, где меняют старый Access Token на новый
[HttpPost("refresh-token")]
public async Task<IActionResult> ОбновитьТокен(RefreshTokenRequest request)
{
// 1. Расшифровываем старый (уже протухший) Access Token, чтобы вытащить оттуда userId (внутри ещё есть инфа).
// 2. Ищем в базе: есть ли у этого userId такой Refresh Token, который нам прислали, и не отозван ли он.
// 3. Если всё чисто — ГЕНЕРИМ НОВУЮ ПАРУ: свежий Access Token и, опционально, новый Refresh Token.
// 4. Старый Refresh Token из базы УДАЛЯЕМ или помечаем использованным. Иначе им можно будет пользоваться вечно — это пиздец какой дырой станет.
// 5. Отдаём новую пару клиенту.
}
4. Безопасность, про которую все забывают, а потом плачут:
- Секретный ключ (Secret) — это твоя королевская печать. Если её засветят — любой сможет штамповать левые пропуска. НИ В КОДЕ, НИ В ГИТЕ. Только в секретных хранилищах или переменных окружения на сервере.
- HTTPS (TLS) — ОБЯЗАТЕЛЬНО. JWT летает открытым текстом. Без HTTPS его снимет любой, кто рядом с WiFi сидит.
- Refresh Token храни ТОЛЬКО в
HttpOnly,Secure,SameSite=Strictкуках (для веб-приложений). Так к нему со стороны JS не подобраться. Если хранишь в БД — шифруй нахер. - Реализуй отзыв токенов. Пользователь сменил пароль? — Все его Refresh Token'ы в мусорку. Заподозрил неладное? — Добавляй
jtiпросроченного Access Token'а в чёрный список (хотя бы на время его жизни). - Не пихай в токен лишнего. Ни паролей, ни номеров карт, ни размера трусов. Только минимум для идентификации. Он же не шифруется, а только подписывается — любой может его прочитать.