Back to Blog
SecurityHTTPSHeadersWeb Security

Security Headers and HTTPS: Protecting Your Web Application

Implement security headers that protect users from common attacks. From CSP to HSTS to secure cookie configuration.

B
Bootspring Team
Engineering
February 10, 2025
5 min read

Security headers are your first line of defense against common web attacks. They're easy to implement and significantly improve your application's security posture.

Essential Security Headers#

Content-Security-Policy (CSP)#

1// Express middleware 2app.use((req, res, next) => { 3 res.setHeader('Content-Security-Policy', [ 4 "default-src 'self'", 5 "script-src 'self' 'unsafe-inline' https://cdn.example.com", 6 "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", 7 "img-src 'self' data: https:", 8 "font-src 'self' https://fonts.gstatic.com", 9 "connect-src 'self' https://api.example.com", 10 "frame-ancestors 'none'", 11 "base-uri 'self'", 12 "form-action 'self'", 13 ].join('; ')); 14 next(); 15});

CSP Directives Explained#

Directive Purpose ───────────────────────────────────────────────────── default-src Fallback for other directives script-src JavaScript sources style-src CSS sources img-src Image sources font-src Font file sources connect-src XHR, WebSocket, fetch destinations frame-src iframe sources frame-ancestors Who can embed this page base-uri Restrict <base> element form-action Form submission destinations upgrade-insecure Upgrade HTTP to HTTPS

Strict CSP with Nonces#

1import crypto from 'crypto'; 2 3app.use((req, res, next) => { 4 // Generate unique nonce per request 5 const nonce = crypto.randomBytes(16).toString('base64'); 6 res.locals.nonce = nonce; 7 8 res.setHeader('Content-Security-Policy', [ 9 "default-src 'self'", 10 `script-src 'self' 'nonce-${nonce}'`, 11 `style-src 'self' 'nonce-${nonce}'`, 12 ].join('; ')); 13 14 next(); 15}); 16 17// In templates 18// <script nonce="<%= nonce %>">...</script>

X-Content-Type-Options#

// Prevent MIME type sniffing res.setHeader('X-Content-Type-Options', 'nosniff');

X-Frame-Options#

// Prevent clickjacking res.setHeader('X-Frame-Options', 'DENY'); // Or allow same origin res.setHeader('X-Frame-Options', 'SAMEORIGIN');

Referrer-Policy#

1// Control referrer information 2res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); 3 4// Options: 5// no-referrer - Never send 6// no-referrer-when-downgrade - Don't send on HTTPS→HTTP 7// same-origin - Only same origin 8// strict-origin - Send origin, not path 9// strict-origin-when-cross-origin - Full URL same origin, origin cross-origin

Permissions-Policy#

1// Control browser features 2res.setHeader('Permissions-Policy', [ 3 'camera=()', // Disable camera 4 'microphone=()', // Disable microphone 5 'geolocation=(self)', // Only self can use geolocation 6 'payment=(self)', // Only self can use payment 7].join(', '));

HTTPS Configuration#

HSTS (HTTP Strict Transport Security)#

1// Force HTTPS 2res.setHeader( 3 'Strict-Transport-Security', 4 'max-age=31536000; includeSubDomains; preload' 5); 6 7// max-age: How long to remember (1 year) 8// includeSubDomains: Apply to all subdomains 9// preload: Submit to browser preload list

Redirect HTTP to HTTPS#

1// Express middleware 2app.use((req, res, next) => { 3 if (req.secure || req.headers['x-forwarded-proto'] === 'https') { 4 return next(); 5 } 6 res.redirect(301, `https://${req.headers.host}${req.url}`); 7}); 8 9// Nginx 10server { 11 listen 80; 12 server_name example.com; 13 return 301 https://$server_name$request_uri; 14}

TLS Configuration#

1# Modern TLS configuration (nginx) 2server { 3 listen 443 ssl http2; 4 5 ssl_certificate /path/to/cert.pem; 6 ssl_certificate_key /path/to/key.pem; 7 8 # Modern protocols only 9 ssl_protocols TLSv1.2 TLSv1.3; 10 11 # Strong ciphers 12 ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; 13 ssl_prefer_server_ciphers off; 14 15 # OCSP Stapling 16 ssl_stapling on; 17 ssl_stapling_verify on; 18 19 # Session configuration 20 ssl_session_timeout 1d; 21 ssl_session_cache shared:SSL:10m; 22 ssl_session_tickets off; 23}

Secure Cookies#

1// Secure cookie configuration 2app.use(session({ 3 name: 'sessionId', 4 secret: process.env.SESSION_SECRET, 5 cookie: { 6 secure: true, // HTTPS only 7 httpOnly: true, // No JavaScript access 8 sameSite: 'strict', // CSRF protection 9 maxAge: 3600000, // 1 hour 10 domain: '.example.com', 11 path: '/', 12 }, 13 resave: false, 14 saveUninitialized: false, 15})); 16 17// Set-Cookie header result: 18// Set-Cookie: sessionId=abc123; Secure; HttpOnly; SameSite=Strict; Path=/
Attribute Purpose ──────────────────────────────────────────────────── Secure Only sent over HTTPS HttpOnly Not accessible via JavaScript SameSite CSRF protection - Strict Only same-site requests - Lax GET from external sites OK - None Cross-site (requires Secure) Domain Which domains receive cookie Path URL path scope Max-Age Lifetime in seconds Expires Absolute expiration date

Using Helmet.js#

1import helmet from 'helmet'; 2 3// Apply all security headers 4app.use(helmet()); 5 6// Or configure individually 7app.use(helmet({ 8 contentSecurityPolicy: { 9 directives: { 10 defaultSrc: ["'self'"], 11 scriptSrc: ["'self'", "https://cdn.example.com"], 12 styleSrc: ["'self'", "'unsafe-inline'"], 13 imgSrc: ["'self'", "data:", "https:"], 14 }, 15 }, 16 hsts: { 17 maxAge: 31536000, 18 includeSubDomains: true, 19 preload: true, 20 }, 21 referrerPolicy: { 22 policy: 'strict-origin-when-cross-origin', 23 }, 24}));

Next.js Configuration#

1// next.config.js 2const securityHeaders = [ 3 { 4 key: 'X-DNS-Prefetch-Control', 5 value: 'on', 6 }, 7 { 8 key: 'Strict-Transport-Security', 9 value: 'max-age=31536000; includeSubDomains', 10 }, 11 { 12 key: 'X-Frame-Options', 13 value: 'SAMEORIGIN', 14 }, 15 { 16 key: 'X-Content-Type-Options', 17 value: 'nosniff', 18 }, 19 { 20 key: 'Referrer-Policy', 21 value: 'strict-origin-when-cross-origin', 22 }, 23 { 24 key: 'Content-Security-Policy', 25 value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'", 26 }, 27]; 28 29module.exports = { 30 async headers() { 31 return [ 32 { 33 source: '/:path*', 34 headers: securityHeaders, 35 }, 36 ]; 37 }, 38};

Testing Security Headers#

Online Tools#

- securityheaders.com - Grade your headers - observatory.mozilla.org - Mozilla security scanner - csp-evaluator.withgoogle.com - CSP analysis

Command Line#

1# Check headers 2curl -I https://example.com 3 4# Detailed SSL check 5openssl s_client -connect example.com:443 -servername example.com 6 7# SSL Labs test 8npx ssllabs-scan example.com

Automated Testing#

1// Jest test for security headers 2describe('Security Headers', () => { 3 it('includes all required security headers', async () => { 4 const response = await fetch('https://example.com'); 5 const headers = response.headers; 6 7 expect(headers.get('Strict-Transport-Security')).toContain('max-age='); 8 expect(headers.get('X-Content-Type-Options')).toBe('nosniff'); 9 expect(headers.get('X-Frame-Options')).toBe('DENY'); 10 expect(headers.get('Content-Security-Policy')).toBeDefined(); 11 expect(headers.get('Referrer-Policy')).toBeDefined(); 12 }); 13});

CSP Reporting#

1// Collect CSP violations 2app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => { 3 console.log('CSP Violation:', req.body); 4 // Send to logging service 5 res.status(204).end(); 6}); 7 8// Enable reporting in CSP 9res.setHeader('Content-Security-Policy', [ 10 "default-src 'self'", 11 "report-uri /csp-report", 12 "report-to csp-endpoint", 13].join('; ')); 14 15res.setHeader('Report-To', JSON.stringify({ 16 group: 'csp-endpoint', 17 max_age: 86400, 18 endpoints: [{ url: '/csp-report' }], 19}));

Conclusion#

Security headers are low-effort, high-impact protections. Start with the essentials (HSTS, CSP, X-Content-Type-Options), then add more as needed. Use tools like Helmet.js to simplify implementation.

Test your headers regularly, monitor CSP reports, and keep your TLS configuration updated. Security is an ongoing process, not a one-time setup.

Share this article

Help spread the word about Bootspring