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

Для чего используется JSON Web Token?

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

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

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

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

JSON Web Token (JWT) в Node.js Backend

JWT — стандартный способ передачи информации о пользователе между клиентом и сервером в защищённом виде. Это один из самых распространённых механизмов аутентификации в современных приложениях.

1. Структура JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

[Header].[Payload].[Signature]

Компоненты:

  • Header — алгоритм и тип (Base64 encoded JSON)
  • Payload — данные пользователя (claims) (Base64 encoded JSON)
  • Signature — подпись для верификации

2. Основные случаи использования

Аутентификация (Authentication)

import jwt from 'jsonwebtoken';

// 1. Пользователь логинится
router.post('/login', async (req, res) => {
  const { email, password } = req.body;
  
  // Проверяем credentials
  const user = await User.findOne({ email });
  if (!user || !user.validatePassword(password)) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  
  // Генерируем JWT
  const token = jwt.sign(
    {
      userId: user.id,
      email: user.email,
      role: user.role
    },
    process.env.JWT_SECRET,
    { expiresIn: '24h' }
  );
  
  res.json({ token });
});

// 2. Клиент отправляет token в header
// Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

// 3. Сервер верифицирует на каждый запрос
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'No token' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

Авторизация (Authorization)

// Проверяем роль пользователя в token
const adminOnly = (req, res, next) => {
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
};

router.delete('/users/:id', authMiddleware, adminOnly, (req, res) => {
  // Только админы могут удалять пользователей
});

3. JWT vs Session

JWT:
- Stateless (не нужна БД для проверки)
- Масштабируется (работает для микросервисов)
- Self-contained (вся информация в token)
- Отправляется в каждом запросе

Session:
- Stateful (требует БД/памяти сервера)
- Проще логировать выход
- Меньше данных в запросе
- Более защищено (token не видно клиенту)

4. Реальный пример

import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';

interface JWTPayload {
  userId: string;
  email: string;
  role: 'user' | 'admin';
  iat?: number;
  exp?: number;
}

class AuthService {
  private jwtSecret = process.env.JWT_SECRET || 'secret';
  private jwtExpiration = '7d';
  
  // Генерация токена
  generateToken(user: User): string {
    const payload: JWTPayload = {
      userId: user.id,
      email: user.email,
      role: user.role
    };
    
    return jwt.sign(payload, this.jwtSecret, {
      expiresIn: this.jwtExpiration,
      algorithm: 'HS256'
    });
  }
  
  // Верификация токена
  verifyToken(token: string): JWTPayload | null {
    try {
      const decoded = jwt.verify(token, this.jwtSecret) as JWTPayload;
      return decoded;
    } catch (error) {
      if (error instanceof jwt.TokenExpiredError) {
        console.log('Token expired');
      } else if (error instanceof jwt.JsonWebTokenError) {
        console.log('Invalid token');
      }
      return null;
    }
  }
  
  // Логин с паролем
  async login(email: string, password: string): Promise<string> {
    const user = await User.findOne({ email });
    
    if (!user) {
      throw new Error('User not found');
    }
    
    const isValid = await bcrypt.compare(password, user.password);
    if (!isValid) {
      throw new Error('Invalid password');
    }
    
    return this.generateToken(user);
  }
  
  // Рефреш токена
  refreshToken(token: string): string | null {
    const payload = this.verifyToken(token);
    
    if (!payload) {
      return null;  // Token невалиден
    }
    
    // Генерируем новый token с теми же данными
    return this.generateToken({
      id: payload.userId,
      email: payload.email,
      role: payload.role
    } as User);
  }
}

5. Рефреш токены (Refresh Tokens)

Для безопасности используют два типа токенов:

interface AuthTokens {
  accessToken: string;      // Короткоживущий (15 мин)
  refreshToken: string;     // Долгоживущий (7 дней)
}

class TokenService {
  generateTokenPair(user: User): AuthTokens {
    // Access token — короткий
    const accessToken = jwt.sign(
      { userId: user.id, email: user.email },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }  // 15 минут
    );
    
    // Refresh token — длинный, хранится в БД
    const refreshToken = jwt.sign(
      { userId: user.id, type: 'refresh' },
      process.env.REFRESH_SECRET,
      { expiresIn: '7d' }   // 7 дней
    );
    
    // Сохраняем refresh token в БД
    db.refreshTokens.create({
      userId: user.id,
      token: refreshToken,
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
    });
    
    return { accessToken, refreshToken };
  }
  
  async refreshAccessToken(refreshToken: string): Promise<string> {
    // Проверяем в БД
    const stored = await db.refreshTokens.findOne({ token: refreshToken });
    
    if (!stored || stored.expiresAt < new Date()) {
      throw new Error('Refresh token expired');
    }
    
    // Генерируем новый access token
    return jwt.sign(
      { userId: stored.userId },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );
  }
}

6. Безопасность JWT

❌ Ошибки:

// Плохо: хранишь пароль в JWT
jwt.sign({ userId: user.id, password: user.password }, secret);

// Плохо: используешь слабый secret
jwt.sign(payload, 'secret123');

// Плохо: не проверяешь expiration
const decoded = jwt.verify(token, secret);

// Плохо: отправляешь secret на клиент
const token = jwt.sign(payload, req.headers['secret']);

✅ Правильно:

// Хорошо: включаешь только необходимые данные
jwt.sign(
  {
    userId: user.id,
    email: user.email,
    role: user.role
  },
  process.env.JWT_SECRET,  // Из env, никогда не в коде!
  {
    expiresIn: '7d',       // Коротко жить
    algorithm: 'HS256'     // Явно указываешь алгоритм
  }
);

// Хорошо: валидируешь на каждый запрос
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Token invalid' });
  }
};

7. Сравнение алгоритмов

// HMAC (Symmetric) — быстро, для собственных систем
jwt.sign(payload, secret, { algorithm: 'HS256' });

// RSA (Asymmetric) — медленнее, для микросервисов
jwt.sign(payload, privateKey, { algorithm: 'RS256' });

8. Best Practices

  • Храни JWT_SECRET в .env файле
  • Используй HTTPS для передачи токенов
  • Устанавливай разумное время жизни (не >24ч)
  • Используй refresh tokens для долгоживущих сессий
  • Логируй попытки использования невалидных токенов
  • Включай минимально необходимую информацию в payload
  • Верифицируй подпись на каждый запрос

JWT — мощный инструмент для масштабируемой аутентификации в микросервисной архитектуре.

Для чего используется JSON Web Token? | PrepBro