← Назад к вопросам

Что такое JSON Web Tolen?

1.7 Middle🔥 142 комментариев
#API и сетевые протоколы#Безопасность

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

JSON Web Token (JWT): Безопасность и практическое применение

JSON Web Token (JWT) — это стандартный способ для передачи информации между системами через URL-safe, компактные токены. Это один из самых важных инструментов в modern backend разработке для аутентификации и авторизации.

Структура JWT

JWT состоит из 3 частей разделенных точками: header.payload.signature

Пример:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Header (закодировано в base64url):

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload (data, тоже base64url):

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622
}

Signature (криптографическая подпись):

HMAC_SHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret_key
)

Как работает JWT

  1. Пользователь логинится:
@Post('/auth/login')
async login(@Body() credentials: LoginDto) {
  const user = await this.userService.validateCredentials(
    credentials.email,
    credentials.password
  );
  
  const payload = {
    sub: user.id,
    email: user.email,
    role: user.role,
  };
  
  const token = this.jwtService.sign(payload, {
    expiresIn: '24h',
    secret: process.env.JWT_SECRET,
  });
  
  return { access_token: token };
}
  1. Клиент отправляет токен в каждом запросе:
const response = await fetch('/api/users/me', {
  headers: {
    'Authorization': `Bearer ${access_token}`
  }
});
  1. Сервер верифицирует токен:
@UseGuards(JwtAuthGuard)
@Get('/me')
async getCurrentUser(@Request() req) {
  // req.user содержит распакованный payload если токен валиден
  return req.user;
}

Преимущества JWT

1. Stateless аутентификация

// JWT: нет нужды хранить сессии на сервере
const token = jwt.sign({ userId: 123 }, secret);

// Любой сервер может верифицировать токен
const decoded = jwt.verify(token, secret); // { userId: 123 }

Это критично для микросервисов и масштабирования.

2. CORS-friendly

JWT отлично работает с CORS потому что это просто строка в header:

// Работает cross-domain
const response = await fetch('https://api.example.com/data', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

3. Мобильные приложения

JWT perfect для мобильных apps потому что нет cookies:

// React Native
const token = await SecureStore.getItemAsync('token');
const response = await fetch(url, {
  headers: { 'Authorization': `Bearer ${token}` }
});

Недостатки и security concerns

1. JWT нельзя invalidate сразу

Токен валиден до истечения срока, даже если пользователь захотел выйти:

// ❌ Плохое решение: сохранить список logout-ов (опять stateful!)
const logoutTokens = new Set();

@Post('/logout')
logout(@Request() req) {
  logoutTokens.add(req.headers.authorization);
}

// ✅ Хорошее решение: использовать refresh tokens
@Post('/logout')
logout(@Request() req) {
  // Invalidate refresh token в БД
  await this.refreshTokenService.invalidate(req.user.id);
}

2. Payload виден любому (base64 не encryption)

// ВАЖНО: JWT зашифрован ПОДПИСЬЮ, а не содержимым
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXQiOiJQYXNzd29yZDEyMyJ9.xxx';

// Декодируем payload
const decoded = jwt.decode(token); // {secret: 'Password123'} — видно!

// НИКОГДА не кладите в JWT чувствительные данные (пароли, кредитные карты)

3. Key rotation сложный

Если приватный ключ скомпрометирован, все issued токены скомпрометированы:

// Решение: использовать JWKS (JSON Web Key Set)
const keys = await fetch('https://example.com/.well-known/jwks.json');
// Сервер может кэшировать и обновлять ключи

Access Token + Refresh Token паттерн

В production я всегда использую двухуровневую систему:

@Post('/auth/login')
async login(@Body() credentials: LoginDto) {
  const user = await this.validateUser(credentials);
  
  // Short-lived access token (15 minutes)
  const accessToken = this.jwtService.sign(
    { sub: user.id, email: user.email },
    { expiresIn: '15m' }
  );
  
  // Long-lived refresh token (7 days) - stored in DB
  const refreshToken = this.jwtService.sign(
    { sub: user.id, tokenVersion: user.tokenVersion },
    { expiresIn: '7d' }
  );
  
  // Сохраняем refresh token в БД
  await this.refreshTokenService.save(user.id, refreshToken);
  
  return {
    accessToken,
    refreshToken,
    expiresIn: 15 * 60 // 15 minutes in seconds
  };
}

@Post('/auth/refresh')
async refresh(@Body() body: { refreshToken: string }) {
  // Верифицируем refresh token
  const decoded = this.jwtService.verify(body.refreshToken);
  
  // Проверяем что токен еще в БД
  const isValid = await this.refreshTokenService.verify(
    decoded.sub,
    body.refreshToken
  );
  
  if (!isValid) {
    throw new UnauthorizedException('Invalid refresh token');
  }
  
  // Выдаем новый access token
  const accessToken = this.jwtService.sign(
    { sub: decoded.sub },
    { expiresIn: '15m' }
  );
  
  return { accessToken };
}

Best Practices

1. Use HTTPS только

// ❌ Никогда не отправляйте JWT по HTTP
// ✅ Всегда HTTPS для защиты от man-in-the-middle

2. Хранение токена на frontend

// ❌ localStorage (уязвимо для XSS)
localStorage.setItem('token', token);

// ✅ HttpOnly cookie (защищено от XSS)
response.cookie('token', token, {
  httpOnly: true,
  secure: true,  // HTTPS only
  sameSite: 'strict', // CSRF protection
});

3. Signature algorithm

// ❌ Плохо: используют RS256 без причины
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });

// ✅ Хорошо: HS256 для простых случаев
const token = jwt.sign(payload, secret, { algorithm: 'HS256' });

// ✅ RS256/ES256 когда нужна асимметричная криптография

4. Claim validation

// Всегда валидируйте все claims
const decoded = jwt.verify(token, secret);

if (!decoded.sub || !decoded.email) {
  throw new UnauthorizedException('Invalid token claims');
}

if (decoded.exp < Date.now() / 1000) {
  throw new UnauthorizedException('Token expired');
}

Real-world implementation

// jwt.strategy.ts
import { Strategy } from 'passport-jwt';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET,
    });
  }
  
  async validate(payload: any) {
    // Passport автоматически верифицирует подпись
    // validate() вызывается только если подпись валидна
    
    const user = await this.userService.findById(payload.sub);
    if (!user) {
      throw new UnauthorizedException();
    }
    
    return user; // Доступна как req.user
  }
}

// auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

// controller.ts
@Controller('api/v1/users')
export class UserController {
  @UseGuards(JwtAuthGuard)
  @Get('/me')
  getProfile(@Request() req) {
    return req.user; // User из validate()
  }
}

Вывод

JWT — это powerful tool для modern аутентификации, особенно в микросервисной архитектуре. Главное — правильно используйте:

  • Access + Refresh tokens
  • HTTPS
  • HttpOnly cookies
  • Валидация всех claims
  • Никогда не кладите sensitive данные

Это foundation современной backend безопасности.

Что такое JSON Web Tolen? | PrepBro