Как осуществляется генерация и валидация JWT-токенов для аутентификации в .NET?

«Как осуществляется генерация и валидация JWT-токенов для аутентификации в .NET?» — вопрос из категории Безопасность, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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: Не помещайте в токен чувствительные данные (пароли).