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

Как проверял токен в приложении?

1.7 Middle🔥 271 комментариев
#Безопасность

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

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

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

Как проверял токен в приложении?

Проверка токенов — одна из ключевых задач аутентификации и авторизации в backend приложениях. За 10+ лет работы с Node.js я использовал различные подходы для валидации токенов.

Типы токенов

Основные типы токенов, которые я проверял:

  • JWT (JSON Web Token) — самодостаточные токены с подписью
  • Bearer токены — простые строки, хранящиеся на сервере
  • API ключи — статичные ключи для machine-to-machine коммуникации
  • Refresh токены — для обновления доступа
  • Session токены — для web приложений

JWT проверка

const jwt = require('jsonwebtoken');

// Проверка JWT токена
function verifyJWT(token) {
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'],
      issuer: 'myapp',
      audience: 'myusers'
    });
    return { valid: true, decoded };
  } catch (error) {
    return {
      valid: false,
      error: error.message // TokenExpiredError, JsonWebTokenError
    };
  }
}

// Middleware для проверки токена
const authMiddleware = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  
  if (!authHeader) {
    return res.status(401).json({ error: 'Missing token' });
  }

  const token = authHeader.replace('Bearer ', '');
  const result = verifyJWT(token);
  
  if (!result.valid) {
    return res.status(401).json({ error: result.error });
  }

  req.user = result.decoded;
  next();
};

Проверка с использованием Passport.js

Для более сложных сценариев использую Passport с стратегиями:

const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;

passport.use(new JwtStrategy(
  {
    jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    secretOrKey: process.env.JWT_SECRET,
    algorithms: ['HS256']
  },
  (payload, done) => {
    // Проверить пользователя в БД
    User.findById(payload.sub)
      .then(user => {
        if (user) return done(null, user);
        return done(null, false);
      })
      .catch(err => done(err));
  }
));

// Использование в маршруте
router.get('/profile', passport.authenticate('jwt'), (req, res) => {
  res.json({ user: req.user });
});

Express.js middleware для проверки

interface JwtPayload {
  sub: string;
  email: string;
  iat: number;
  exp: number;
}

function createAuthMiddleware(secret: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    try {
      const token = req.headers.authorization?.split(' ')[1];
      
      if (!token) {
        return res.status(401).json({ error: 'Unauthorized' });
      }

      const payload = jwt.verify(token, secret) as JwtPayload;
      
      // Проверить дополнительные условия
      if (payload.exp < Date.now() / 1000) {
        return res.status(401).json({ error: 'Token expired' });
      }

      req.user = payload;
      next();
    } catch (error) {
      if (error instanceof jwt.TokenExpiredError) {
        return res.status(401).json({ error: 'Token expired' });
      }
      if (error instanceof jwt.JsonWebTokenError) {
        return res.status(401).json({ error: 'Invalid token' });
      }
      res.status(500).json({ error: 'Internal error' });
    }
  };
}

Проверка с учётом роли пользователя (RBAC)

const authorize = (roles = []) => {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden' });
    }

    next();
  };
};

// Использование
router.delete('/users/:id', 
  authenticate,
  authorize(['admin']),
  deleteUserHandler
);

Redis для кеширования токенов

Для высоконагруженных систем кешировал информацию о токенах:

const redis = require('redis');
const client = redis.createClient();

async function verifyTokenWithCache(token) {
  // Проверить в Redis
  const cached = await client.get(`token:${token}`);
  if (cached) {
    return JSON.parse(cached);
  }

  // Если нет в кеше, проверить подпись
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  
  // Сохранить на (exp - now) секунд
  const ttl = decoded.exp - Math.floor(Date.now() / 1000);
  await client.setex(`token:${token}`, ttl, JSON.stringify(decoded));
  
  return decoded;
}

Проверка API ключей

const apiKeyMiddleware = (req, res, next) => {
  const apiKey = req.headers['x-api-key'];
  
  if (!apiKey) {
    return res.status(401).json({ error: 'Missing API key' });
  }

  // Проверить в БД (хеш для безопасности)
  const hashedKey = crypto
    .createHash('sha256')
    .update(apiKey)
    .digest('hex');

  ApiKey.findOne({ hash: hashedKey })
    .then(record => {
      if (!record) {
        return res.status(401).json({ error: 'Invalid API key' });
      }

      if (record.expiresAt < new Date()) {
        return res.status(401).json({ error: 'API key expired' });
      }

      req.apiKeyUser = record.userId;
      next();
    })
    .catch(() => res.status(500).json({ error: 'Error' }));
};

Черный список токенов (отзыв)

// Logout — добавить в черный список
router.post('/logout', authenticate, (req, res) => {
  const token = req.headers.authorization.split(' ')[1];
  const decoded = jwt.decode(token);
  const ttl = decoded.exp - Math.floor(Date.now() / 1000);
  
  // Добавить в Redis на время до истечения
  client.setex(`blacklist:${token}`, ttl, 'true');
  
  res.json({ message: 'Logged out' });
});

// Проверка при верификации
function verifyJWT(token) {
  const inBlacklist = client.get(`blacklist:${token}`);
  if (inBlacklist) {
    return { valid: false, error: 'Token revoked' };
  }

  try {
    return { valid: true, decoded: jwt.verify(token, secret) };
  } catch (err) {
    return { valid: false, error: err.message };
  }
}

Best Practices при проверке токенов

  • Всегда используй HTTPS для передачи токенов
  • Никогда не логируй токены в logs
  • Установи правильное время жизни (exp) для токенов
  • Используй секретные ключи из переменных окружения
  • Кешируй результаты проверки в Redis для оптимизации
  • Реализуй механизм отзыва токенов (blacklist или whitelist)
  • Валидируй все claims в JWT (iss, aud, sub, exp)
  • Используй асимметричную криптографию (RS256) для микросервисов
  • Следи за временем истечения и предупреждай пользователей
  • Логируй попытки с невалидными токенами для безопасности

Правильная проверка токенов — основа безопасности приложения. Я всегда использовал комбинацию проверок уровня приложения, кеширования и мониторинга для максимальной защиты.