Helmet Complete Guide | Secure Express Apps with HTTP Headers

Helmet Complete Guide | Secure Express Apps with HTTP Headers

이 글의 핵심

Helmet helps secure Express apps by setting various HTTP headers. It's a collection of 15 smaller middleware functions that set security-related headers.

Introduction

Helmet helps secure Express apps by setting HTTP security headers. It’s not a silver bullet, but it provides an important layer of defense against common attacks.

Why Helmet?

Without Helmet, your Express app exposes information and is vulnerable:

// Without Helmet
app.get('/', (req, res) => {
  res.send('Hello');
});

// Response headers:
// X-Powered-By: Express (exposes framework)
// (missing security headers)

With Helmet:

app.use(helmet());

// Response headers now include:
// Content-Security-Policy: ...
// X-DNS-Prefetch-Control: off
// X-Frame-Options: SAMEORIGIN
// Strict-Transport-Security: ...
// X-Content-Type-Options: nosniff
// (and more!)

1. Installation

npm install helmet

2. Basic Usage

const express = require('express');
const helmet = require('helmet');

const app = express();

// Use Helmet with defaults
app.use(helmet());

app.get('/', (req, res) => {
  res.send('Hello, secure world!');
});

app.listen(3000);

3. What Helmet Does

Helmet is a collection of 15 smaller middleware:

app.use(helmet());

// Equivalent to:
app.use(helmet.contentSecurityPolicy());
app.use(helmet.crossOriginEmbedderPolicy());
app.use(helmet.crossOriginOpenerPolicy());
app.use(helmet.crossOriginResourcePolicy());
app.use(helmet.dnsPrefetchControl());
app.use(helmet.frameguard());
app.use(helmet.hidePoweredBy());
app.use(helmet.hsts());
app.use(helmet.ieNoOpen());
app.use(helmet.noSniff());
app.use(helmet.originAgentCluster());
app.use(helmet.permittedCrossDomainPolicies());
app.use(helmet.referrerPolicy());
app.use(helmet.xssFilter());

4. Content Security Policy (CSP)

CSP prevents XSS attacks by controlling which resources can be loaded:

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.example.com"],
    styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
    imgSrc: ["'self'", "data:", "https:"],
    fontSrc: ["'self'", "https://fonts.gstatic.com"],
    connectSrc: ["'self'", "https://api.example.com"],
    frameSrc: ["'none'"],
    objectSrc: ["'none'"],
    upgradeInsecureRequests: [],
  },
}));
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    styleSrc: ["'self'"],
    imgSrc: ["'self'"],
    fontSrc: ["'self'"],
    connectSrc: ["'self'"],
    frameSrc: ["'none'"],
    objectSrc: ["'none'"],
  },
}));

CSP for React/Vue Apps

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'"], // React inline scripts
    styleSrc: ["'self'", "'unsafe-inline'"], // CSS-in-JS
    imgSrc: ["'self'", "data:", "https:"],
    connectSrc: ["'self'", process.env.API_URL],
  },
}));

5. HTTP Strict Transport Security (HSTS)

Forces HTTPS connections:

app.use(helmet.hsts({
  maxAge: 31536000, // 1 year in seconds
  includeSubDomains: true,
  preload: true,
}));

// Only enable in production with HTTPS
if (process.env.NODE_ENV === 'production') {
  app.use(helmet.hsts({
    maxAge: 31536000,
    includeSubDomains: true,
  }));
}

6. X-Frame-Options

Prevents clickjacking:

// Deny all framing
app.use(helmet.frameguard({ action: 'deny' }));

// Allow same origin
app.use(helmet.frameguard({ action: 'sameorigin' }));

// Allow specific domain
app.use(helmet.frameguard({
  action: 'allow-from',
  domain: 'https://example.com'
}));

7. X-Content-Type-Options

Prevents MIME sniffing:

app.use(helmet.noSniff());

// Sets header:
// X-Content-Type-Options: nosniff

8. Referrer Policy

Controls Referer header:

app.use(helmet.referrerPolicy({
  policy: 'strict-origin-when-cross-origin'
}));

// Options:
// - no-referrer
// - no-referrer-when-downgrade
// - origin
// - origin-when-cross-origin
// - same-origin
// - strict-origin
// - strict-origin-when-cross-origin (recommended)
// - unsafe-url

9. Hide Powered By

Remove X-Powered-By header:

app.use(helmet.hidePoweredBy());

// Or manually:
app.disable('x-powered-by');

// Or fake it:
app.use(helmet.hidePoweredBy({ setTo: 'PHP 4.2.0' }));

10. Custom Configuration

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdn.example.com"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
  },
  frameguard: {
    action: 'deny',
  },
  referrerPolicy: {
    policy: 'strict-origin-when-cross-origin',
  },
}));

11. Disable Specific Middleware

app.use(helmet({
  contentSecurityPolicy: false, // Disable CSP
  frameguard: false, // Disable X-Frame-Options
}));

12. Production Configuration

const express = require('express');
const helmet = require('helmet');

const app = express();

// Production-ready Helmet config
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", process.env.CDN_URL],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", process.env.API_URL],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
  frameguard: {
    action: 'deny',
  },
  referrerPolicy: {
    policy: 'strict-origin-when-cross-origin',
  },
}));

// Additional security
app.disable('x-powered-by');

// Trust proxy (if behind reverse proxy)
app.set('trust proxy', 1);

app.listen(process.env.PORT || 3000);

13. With CORS

const helmet = require('helmet');
const cors = require('cors');

// Apply Helmet first
app.use(helmet());

// Then CORS
app.use(cors({
  origin: process.env.FRONTEND_URL,
  credentials: true,
}));

14. Testing Security Headers

# Check headers with curl
curl -I https://example.com

# Look for:
# Content-Security-Policy: ...
# Strict-Transport-Security: max-age=31536000; includeSubDomains
# X-Frame-Options: DENY
# X-Content-Type-Options: nosniff
# Referrer-Policy: strict-origin-when-cross-origin

Security Scanners

# Mozilla Observatory
# https://observatory.mozilla.org/

# Security Headers
# https://securityheaders.com/

# SSL Labs
# https://www.ssllabs.com/ssltest/

15. Common Issues

Issue 1: CSP Blocking Inline Scripts

// Problem: React/Vue inline scripts blocked
<script>window.__INITIAL_STATE__ = {...}</script>

// Solution 1: Use nonce
app.use((req, res, next) => {
  res.locals.nonce = crypto.randomBytes(16).toString('base64');
  next();
});

app.use(helmet.contentSecurityPolicy({
  directives: {
    scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
  },
}));

// In template:
<script nonce="<%= nonce %>">...</script>

// Solution 2: Allow unsafe-inline (less secure)
app.use(helmet.contentSecurityPolicy({
  directives: {
    scriptSrc: ["'self'", "'unsafe-inline'"],
  },
}));

Issue 2: HSTS Breaking Local Development

// Only enable HSTS in production
if (process.env.NODE_ENV === 'production') {
  app.use(helmet.hsts({
    maxAge: 31536000,
  }));
}

Issue 3: CSP Blocking CDN Resources

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: [
      "'self'",
      "https://cdn.jsdelivr.net",
      "https://cdnjs.cloudflare.com",
    ],
    styleSrc: [
      "'self'",
      "https://fonts.googleapis.com",
    ],
  },
}));

16. Best Practices

1. Use Helmet in All Environments

// Development
app.use(helmet({
  contentSecurityPolicy: false, // Easier debugging
}));

// Production
app.use(helmet()); // Full security

2. Test Thoroughly

// Test CSP in report-only mode first
app.use(helmet.contentSecurityPolicy({
  directives: { /* ... */ },
  reportOnly: true, // Only report violations, don't block
}));

3. Monitor CSP Violations

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    reportUri: '/api/csp-violation-report',
  },
}));

app.post('/api/csp-violation-report', (req, res) => {
  console.log('CSP Violation:', req.body);
  // Log to monitoring service
  res.status(204).end();
});

Summary

Helmet provides essential security headers:

  • 15 security middlewares in one package
  • CSP prevents XSS attacks
  • HSTS enforces HTTPS
  • Frameguard prevents clickjacking
  • Production-ready with minimal config

Key Takeaways:

  1. Always use Helmet in production
  2. Configure CSP for your specific app
  3. Enable HSTS only with HTTPS
  4. Test security headers regularly
  5. Monitor CSP violations

Next Steps:

  • Configure CORS
  • Implement Passport Auth
  • Secure Express API

Resources: