
JavaScript Security: Implementing Secure Authentication with JWT and Rate Limiting
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 Measure | JWT Benefits | JWT Drawbacks | Alternative Approach | Security Level |
|---|---|---|---|---|
| Token Expiration | Stateless session management | Tokens remain valid until expiration | Session storage | Medium |
| Refresh Tokens | Prevents frequent re-authentication | Requires secure storage | OAuth 2.0 | High |
| Rate Limiting | Prevents brute force attacks | May impact legitimate users | CAPTCHA | High |
| Content Security | Blocks XSS attacks | Complex configuration | Input sanitization | High |
| Secure Headers | Prevents common vulnerabilities | Requires updates | Manual headers | Medium |
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');
}