CORS and Security Headers
Handling cross-origin requests and security
Challenge
Section titled “Challenge”Safely handle cross-origin requests and protect against common web vulnerabilities.
Explanation
Section titled “Explanation”CORS controls which origins can access your resources. Security headers provide additional protection.
CORS Basics
Section titled “CORS Basics”const express = require('express');const cors = require('cors');const app = express();
// Enable CORS for all routesapp.use(cors());
// CORS for specific routeapp.get('/api/data', cors(), (req, res) => { res.json({ data: 'public' });});
// Configure CORS optionsapp.use(cors({ origin: 'https://example.com', methods: ['GET', 'POST'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, maxAge: 3600}));
// Preflight requestsapp.options('*', cors());Manual CORS Headers
Section titled “Manual CORS Headers”app.use((req, res, next) => { const origin = req.headers.origin; const allowedOrigins = [ 'http://localhost:3000', 'https://example.com' ];
if (allowedOrigins.includes(origin)) { res.header('Access-Control-Allow-Origin', origin); }
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); res.header('Access-Control-Allow-Credentials', 'true'); res.header('Access-Control-Max-Age', '3600');
if (req.method === 'OPTIONS') { return res.sendStatus(200); }
next();});Security Headers
Section titled “Security Headers”const helmet = require('helmet');
app.use(helmet());
// Or configure manuallyapp.use((req, res, next) => { // Prevent clickjacking res.header('X-Frame-Options', 'DENY');
// Prevent MIME type sniffing res.header('X-Content-Type-Options', 'nosniff');
// XSS protection res.header('X-XSS-Protection', '1; mode=block');
// Content Security Policy res.header( 'Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" );
// Referrer Policy res.header('Referrer-Policy', 'strict-origin-when-cross-origin');
// Permissions Policy res.header( 'Permissions-Policy', 'camera=(), microphone=(), geolocation=()' );
next();});CSRF Protection
Section titled “CSRF Protection”const csrf = require('csurf');const cookieParser = require('cookie-parser');const session = require('express-session');
app.use(cookieParser());app.use(session({ secret: 'secret' }));
const csrfProtection = csrf({ cookie: false });
// Generate CSRF tokenapp.get('/form', csrfProtection, (req, res) => { res.json({ csrfToken: req.csrfToken() });});
// Verify CSRF tokenapp.post('/form', csrfProtection, (req, res) => { res.json({ message: 'Form accepted' });});Content Security Policy
Section titled “Content Security Policy”const csp = require('helmet-csp');
app.use(csp({ directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'", 'cdn.example.com'], scriptSrc: ["'self'", 'cdn.example.com'], imgSrc: ["'self'", 'data:', 'https:'], frameSrc: ["'none'"], objectSrc: ["'none'"] }}));HTTPS and Secure Cookies
Section titled “HTTPS and Secure Cookies”app.set('trust proxy', 1);
app.use(session({ secret: 'secret', cookie: { secure: true, // HTTPS only httpOnly: true, // No JavaScript access sameSite: 'strict',// CSRF protection maxAge: 1000 * 60 * 60 * 24 // 24 hours }}));
// Enforce HTTPSapp.use((req, res, next) => { if (req.header('x-forwarded-proto') !== 'https') { res.redirect(`https://${req.header('host')}${req.url}`); } else { next(); }});Rate Limiting
Section titled “Rate Limiting”const rateLimit = require('express-rate-limit');
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // 100 requests message: 'Too many requests'});
app.use(limiter);
// Specific routesconst loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, skipSuccessfulRequests: true});
app.post('/login', loginLimiter, (req, res) => { // Handle login});Input Validation
Section titled “Input Validation”const { body, validationResult } = require('express-validator');
app.post('/user', [ body('email').isEmail().normalizeEmail(), body('password').isLength({ min: 8 }), body('name').trim().escape()], (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); }
// Process validated input});