Core Security Implementation

JWT-based authentication provides stateless session management while maintaining security through cryptographic signing. The following implementation demonstrates secure token handling with proper error management and security headers.

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const rateLimit = require('express-rate-limit');

// Secure JWT configuration
const jwtConfig = {
  secret: process.env.JWT_SECRET || 'your-super-secret-key-here',
  expiresIn: '1h',
  refreshExpiresIn: '7d'
};

// Rate limiting for authentication endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // limit each IP to 5 requests per windowMs
  message: 'Too many authentication attempts, please try again later',
  standardHeaders: true,
  legacyHeaders: false,
});

// Secure token generation
function generateTokens(user) {
  const payload = {
    userId: user.id,
    username: user.username,
    role: user.role
  };

  const accessToken = jwt.sign(payload, jwtConfig.secret, {
    expiresIn: jwtConfig.expiresIn
  });

  const refreshToken = jwt.sign(
    { userId: user.id }, 
    jwtConfig.secret + '_refresh', 
    { expiresIn: jwtConfig.refreshExpiresIn }
  );

  return { accessToken, refreshToken };
}

// Token validation middleware
function verifyToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'Access token required' });
  }

  jwt.verify(token, jwtConfig.secret, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Invalid or expired token' });
    }
    req.user = user;
    next();
  });
}

Token Management Best Practices

Effective token management prevents session hijacking and ensures secure application state. The following code demonstrates secure refresh token handling with proper database storage and validation.

// Refresh token storage with database
class TokenStorage {
  constructor() {
    this.refreshTokens = new Set();
  }

  async storeRefreshToken(userId, refreshToken) {
    // Store in database with expiration
    await db.refreshTokens.create({
      userId,
      token: refreshToken,
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
    });
    
    // Add to in-memory cache for immediate validation
    this.refreshTokens.add(refreshToken);
  }

  async validateRefreshToken(refreshToken) {
    // Check in-memory cache first
    if (this.refreshTokens.has(refreshToken)) {
      // Verify against database
      const tokenRecord = await db.refreshTokens.findOne({
        where: { token: refreshToken }
      });
      
      if (tokenRecord && tokenRecord.expiresAt > new Date()) {
        return tokenRecord.userId;
      }
      
      // Remove invalid token
      this.refreshTokens.delete(refreshToken);
      return null;
    }
    return null;
  }

  async revokeRefreshToken(refreshToken) {
    await db.refreshTokens.destroy({ where: { token: refreshToken } });
    this.refreshTokens.delete(refreshToken);
  }
}

// Secure refresh endpoint
app.post('/auth/refresh', authLimiter, async (req, res) => {
  const { refreshToken } = req.body;
  
  if (!refreshToken) {
    return res.status(401).json({ error: 'Refresh token required' });
  }

  try {
    const userId = await tokenStorage.validateRefreshToken(refreshToken);
    
    if (!userId) {
      return res.status(403).json({ error: 'Invalid refresh token' });
    }

    // Generate new tokens
    const user = await db.users.findByPk(userId);
    const { accessToken, refreshToken: newRefreshToken } = generateTokens(user);
    
    // Store new refresh token
    await tokenStorage.storeRefreshToken(userId, newRefreshToken);
    
    res.json({ accessToken, refreshToken: newRefreshToken });
  } catch (error) {
    res.status(500).json({ error: 'Token refresh failed' });
  }
});

Security Headers and Response Protection

Implementing proper security headers and response sanitization prevents common vulnerabilities like XSS attacks and information disclosure.

const helmet = require('helmet');

// Security middleware configuration
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'"],
      fontSrc: ["'self'", "https:"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"]
    }
  },
  crossOriginEmbedderPolicy: false,
  crossOriginOpenerPolicy: false,
  crossOriginResourcePolicy: false
}));

// Secure response handling
function secureResponse(res, data, status = 200) {
  // Sanitize sensitive data
  const sanitizedData = {
    ...data,
    // Remove any sensitive fields that shouldn't be exposed
    password: undefined,
    refreshToken: undefined
  };

  // Set security headers
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  
  return res.status(status).json(sanitizedData);
}

// Input validation and sanitization
function sanitizeInput(input) {
  if (typeof input === 'string') {
    // Remove potentially dangerous characters
    return input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
  }
  return input;
}

Comparison of Security Approaches

Security MeasureJWT BenefitsJWT DrawbacksAlternative ApproachSecurity Level
Token ExpirationStateless session managementTokens remain valid until expirationSession storageMedium
Refresh TokensPrevents frequent re-authenticationRequires secure storageOAuth 2.0High
Rate LimitingPrevents brute force attacksMay impact legitimate usersCAPTCHAHigh
Content SecurityBlocks XSS attacksComplex configurationInput sanitizationHigh
Secure HeadersPrevents common vulnerabilitiesRequires updatesManual headersMedium

Advanced Security Patterns

Implementing advanced security patterns enhances protection against sophisticated attacks while maintaining usability.

// IP-based rate limiting with geographical awareness
const ipRateLimit = rateLimit({
  windowMs: 10 * 60 * 1000, // 10 minutes
  max: 10,
  keyGenerator: (req) => {
    // Use IP + user agent for more granular control
    return `${req.ip}-${req.get('User-Agent')}`;
  },
  handler: (req, res) => {
    res.status(429).json({
      error: 'Too many requests from this IP',
      retryAfter: 10 * 60 // 10 minutes
    });
  }
});

// Multi-factor authentication integration
async function verifyMFA(req, res, next) {
  const { mfaToken, userId } = req.body;
  
  if (!mfaToken) {
    return res.status(400).json({ error: 'MFA token required' });
  }

  try {
    const isValid = await verifyTOTPToken(userId, mfaToken);
    if (!isValid) {
      return res.status(401).json({ error: 'Invalid MFA token' });
    }
    next();
  } catch (error) {
    res.status(500).json({ error: 'MFA verification failed' });
  }
}

// Session binding for enhanced security
function bindSession(req, res, next) {
  const userAgent = req.get('User-Agent');
  const ip = req.ip;
  
  req.session.userAgent = userAgent;
  req.session.ip = ip;
  
  // Verify session integrity
  if (req.session.userAgent !== userAgent || req.session.ip !== ip) {
    req.session.destroy();
    return res.status(401).json({ error: 'Session integrity check failed' });
  }
  
  next();
}

Error Handling and Logging

Proper error handling prevents information leakage while maintaining security monitoring capabilities.

// Secure error handling
app.use((err, req, res, next) => {
  // Log security-related errors
  if (err.name === 'UnauthorizedError' || err.status === 403) {
    logger.warn(`Security violation: ${req.ip} - ${req.path}`);
  }
  
  // Send generic error response
  if (err.status >= 500) {
    res.status(500).json({ error: 'Internal server error' });
  } else {
    res.status(err.status || 400).json({ 
      error: err.message || 'Bad request' 
    });
  }
});

// Security audit logging
function logSecurityEvent(eventType, details) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    eventType,
    ip: details.ip,
    userAgent: details.userAgent,
    userId: details.userId,
    details: details.details
  };
  
  // Write to secure audit log
  fs.appendFileSync('security-audit.log', JSON.stringify(logEntry) + '\n');
}

Learn more with useful resources