Using CSP (Content Security Policy) with React

Loading

Content Security Policy (CSP) is a critical security layer that helps prevent XSS, clickjacking, and other code injection attacks. Here’s how to properly implement CSP with React applications:

Basic CSP Setup for React

1. Minimal Production CSP

<!-- public/index.html -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self';
               script-src 'self' 'unsafe-inline' 'unsafe-eval';
               style-src 'self' 'unsafe-inline';
               img-src 'self' data:;
               connect-src 'self' https://api.example.com;
               font-src 'self';
               frame-ancestors 'none';
               form-action 'self';">

2. Using Helmet in Express

const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"], // Required for React
    styleSrc: ["'self'", "'unsafe-inline'"], // Required for CSS-in-JS
    imgSrc: ["'self'", "data:"],
    connectSrc: ["'self'", "https://api.example.com"],
    fontSrc: ["'self'"],
    frameSrc: ["'none'"],
    formAction: ["'self'"]
  }
}));

React-Specific CSP Considerations

1. Handling Development Mode

// Dynamic CSP based on environment
const isDev = process.env.NODE_ENV === 'development';

const cspConfig = {
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: [
      "'self'",
      isDev && "'unsafe-inline'",
      isDev && "'unsafe-eval'",
      "https://cdn.example.com"
    ].filter(Boolean),
    // ... other directives
  }
};

app.use(helmet.contentSecurityPolicy(cspConfig));

2. Webpack Configuration for CSP

// webpack.config.js
module.exports = {
  // ... other config
  devServer: {
    headers: {
      "Content-Security-Policy": "default-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' ws://localhost:*"
    }
  }
};

Advanced CSP Techniques

1. Nonce-Based CSP for Production

// Server-side nonce generation
app.use((req, res, next) => {
  res.locals.nonce = crypto.randomBytes(16).toString('hex');
  next();
});

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

// In your HTML template
<script nonce="<%= nonce %>">
  // Inline scripts that need to execute
</script>

2. Hash-Based CSP

// Calculate SHA hash of your inline script
const crypto = require('crypto');
const fs = require('fs');

const script = fs.readFileSync('path/to/inline-script.js', 'utf8');
const hash = crypto.createHash('sha256').update(script).digest('base64');

// In your CSP
scriptSrc: [
  "'self'",
  `'sha256-${hash}'`
]

Common React Patterns with CSP

1. Styled Components/CSS-in-JS

// Required CSP directives
styleSrc: [
  "'self'",
  "'unsafe-inline'", // Required for most CSS-in-JS solutions
  "https://fonts.googleapis.com"
]

2. External Libraries (Google Analytics, etc.)

scriptSrc: [
  "'self'",
  "https://www.google-analytics.com",
  "https://www.googletagmanager.com"
],
connectSrc: [
  "'self'",
  "https://www.google-analytics.com"
],
imgSrc: [
  "'self'",
  "https://www.google-analytics.com",
  "data:"
]

3. WebSocket Connections (for HMR)

// Development-only CSP addition
connectSrc: [
  "'self'",
  process.env.NODE_ENV === 'development' && "ws://localhost:*"
].filter(Boolean)

CSP Error Reporting

1. Report-Only Mode

// Good for initial rollout
app.use(helmet.contentSecurityPolicy({
  directives: {
    // ... your directives
  },
  reportOnly: true
}));

2. Violation Reporting Endpoint

directives: {
  // ... other directives
  reportUri: '/api/csp-violation',
  reportTo: '/api/csp-violation'
}

// Handle reports
app.post('/api/csp-violation', (req, res) => {
  console.warn('CSP violation:', req.body);
  res.status(204).end();
});

Testing Your CSP

1. Browser DevTools

  • Check Console for CSP violations
  • Network tab shows CSP headers

2. Automated Testing

// Jest/Node test example
test('should have proper CSP header', async () => {
  const response = await fetch('http://localhost:3000');
  const cspHeader = response.headers.get('content-security-policy');

  expect(cspHeader).toContain("default-src 'self'");
  expect(cspHeader).toContain("frame-ancestors 'none'");
});

3. Online Validators

Migration Strategy

  1. Start with Report-Only Mode
  2. Monitor violations in production
  3. Gradually tighten policies
  4. Move to enforced CSP once stable

Troubleshooting Common Issues

1. React Errors Due to Strict CSP

// If you see errors like "Refused to evaluate a string as JavaScript"
// You may need to add:
scriptSrc: [
  "'self'",
  "'unsafe-inline'", // For React's required inline scripts
  "'unsafe-eval'"    // For webpack runtime
]

2. Styled Components Not Working

// Add this to your CSP:
styleSrc: [
  "'self'",
  "'unsafe-inline'", // Required for styled-components
  "https://fonts.googleapis.com"
]

3. Font Loading Issues

fontSrc: [
  "'self'",
  "https://fonts.gstatic.com",
  "data:" // For inline font data
]

Complete CSP Example for React

// Production-ready CSP configuration
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'none'"], // Default deny all
    scriptSrc: [
      "'self'",
      "'unsafe-inline'", // Required for React
      "'unsafe-eval'",   // Required for webpack
      "https://cdn.example.com"
    ],
    styleSrc: [
      "'self'",
      "'unsafe-inline'", // Required for CSS-in-JS
      "https://fonts.googleapis.com"
    ],
    imgSrc: [
      "'self'",
      "data:",
      "https://*.example.com"
    ],
    connectSrc: [
      "'self'",
      "https://api.example.com",
      "https://analytics.example.com"
    ],
    fontSrc: [
      "'self'",
      "data:",
      "https://fonts.gstatic.com"
    ],
    frameSrc: ["'none'"], // Prevent clickjacking
    formAction: ["'self'"],
    baseUri: ["'self'"],
    objectSrc: ["'none'"], // No Flash/plugins
    reportUri: '/api/csp-violation'
  },
  reportOnly: false // Enforce CSP after testing
}));

Remember that implementing CSP is an iterative process – start with report-only mode, monitor violations, and gradually tighten your policy. The most secure CSP is one that balances security with actual functionality requirements.

Leave a Reply

Your email address will not be published. Required fields are marked *