CORS and CSP Configuration

Essential Browser Security Mechanisms for Modern Web Applications

Introduction to Browser Security Models

Web browsers implement several security mechanisms to protect users as they browse the internet. Today, we'll focus on two critical browser security features that you need to understand as full-stack developers:

Both of these mechanisms build upon a fundamental browser security concept: the Same-Origin Policy.

flowchart TD A[Browser Security Models] --> B[Same-Origin Policy] B --> C[CORS\nCross-Origin Resource Sharing] B --> D[CSP\nContent Security Policy] C --> C1[Controls API access] D --> D1[Controls resource loading] C1 --> C2[Relaxes Same-Origin Policy] D1 --> D2[Restricts content sources]

Understanding the Same-Origin Policy

The Same-Origin Policy (SOP) is a critical security mechanism implemented by web browsers that restricts how a document or script loaded from one origin can interact with resources from another origin.

An origin is defined by the combination of:

Origin Examples

URL Origin
https://example.com/page.html https://example.com
https://example.com:443/page.html https://example.com
http://example.com/page.html http://example.com
https://subdomain.example.com/page.html https://subdomain.example.com
https://example.com:8080/page.html https://example.com:8080

What the Same-Origin Policy Restricts

Under the Same-Origin Policy, a web page can only make direct requests to resources from the same origin. This prevents malicious websites from:

What is Allowed Across Origins

The Same-Origin Policy does allow some cross-origin interactions:

Real-World Analogy

Think of the Same-Origin Policy like a building's security system. People from Company A can freely interact with Company A's offices, use the meeting rooms, and access documents. But they cannot enter Company B's offices in the same building without explicit permission, even though both companies share the building (the web browser).

Cross-Origin Resource Sharing (CORS)

While the Same-Origin Policy provides essential security, modern web applications often need to make requests across different origins. CORS is a mechanism that allows a server to selectively relax the Same-Origin Policy, enabling cross-origin requests in a controlled manner.

Browser Frontend App app.example.com API Server api.example.com 1. Request with Origin: app.example.com Check CORS Policy 2. Response with CORS headers

How CORS Works

When a website makes a cross-origin request, the browser automatically adds an Origin header to the request. The server checks this header and decides whether to allow the request by sending appropriate CORS headers in the response.

Simple CORS Request & Response


// Browser automatically adds:
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com

// Server responds with:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json

{"data": "This is accessible cross-origin"}
                

CORS Headers

The main CORS response headers are:

Preflight Requests

For "non-simple" requests (like those using PUT/DELETE methods, custom headers, or content types other than application/x-www-form-urlencoded, multipart/form-data, or text/plain), browsers send a preflight request using the OPTIONS method to check if the actual request is allowed.

Preflight Request & Response


// Preflight request (automatic by browser)
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

// Preflight response from server
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
                

Only after this preflight check passes will the actual PUT request be sent.

Implementing CORS in Express.js

In Express.js applications, the cors middleware makes it easy to implement CORS:

Basic CORS Implementation


const express = require('express');
const cors = require('cors');
const app = express();

// Allow all origins (not recommended for production)
app.use(cors());

// OR with specific options
app.use(cors({
    origin: 'https://app.example.com', // Allow only this origin
    methods: ['GET', 'POST'],          // Allow only GET and POST
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true                  // Allow cookies
}));

// Route-specific CORS
app.get('/api/public-data', cors(), (req, res) => {
    res.json({ data: 'This API is accessible cross-origin' });
});

// Restricted API with custom CORS configuration
const restrictedApiCors = cors({
    origin: ['https://admin.example.com', 'https://internal.example.com'],
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    credentials: true
});

app.put('/api/protected-data', restrictedApiCors, (req, res) => {
    // Only allowed origins can access this endpoint
    res.json({ data: 'Protected data updated' });
});
                

Handling Dynamic Origins

For more complex scenarios, you can use a function for the origin option:


app.use(cors({
    origin: function(origin, callback) {
        // Allow requests with no origin (like mobile apps, curl)
        if (!origin) return callback(null, true);
        
        const allowedOrigins = [
            'https://app.example.com',
            'https://admin.example.com',
            /\.example\.net$/  // Allow all subdomains of example.net
        ];
        
        // Check if origin is allowed
        let allowed = false;
        for (let allowedOrigin of allowedOrigins) {
            if (typeof allowedOrigin === 'string' && origin === allowedOrigin) {
                allowed = true;
                break;
            } else if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {
                allowed = true;
                break;
            }
        }
        
        if (allowed) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    credentials: true
}));
                

Real-World CORS Scenario: Third-Party API Integration

Imagine you're building a weather dashboard app at weather-app.com that integrates with a third-party weather API at weather-api.com. Without CORS, your frontend JavaScript couldn't directly fetch data from the weather API. You'd need to proxy all requests through your own backend server. With CORS properly configured on the weather API, your frontend can make direct requests, reducing latency and simplifying your architecture.

Content Security Policy (CSP)

Content Security Policy is a browser security feature that helps prevent cross-site scripting (XSS), clickjacking, and other code injection attacks by controlling which resources can be loaded and executed on your web page.

flowchart TD A[Content Security Policy] --> B[Restrict Resource Origins] A --> C[Mitigate XSS Attacks] A --> D[Report Violations] B --> B1[Scripts] B --> B2[Styles] B --> B3[Images] B --> B4[Fonts] B --> B5[Media] B --> B6[Objects] C --> C1[Block inline scripts] C --> C2[Block eval()] C --> C3[Block string-to-code] D --> D1[Report-Only Mode] D --> D2[Violation Reports]

How CSP Works

CSP works by declaring approved sources of content that the browser can load. This is done through the Content-Security-Policy HTTP header or a <meta> tag in the HTML.

Setting CSP via HTTP Header


// Express.js example
app.use((req, res, next) => {
    res.setHeader('Content-Security-Policy', 
        "default-src 'self'; " +
        "script-src 'self' https://trusted-cdn.com; " +
        "style-src 'self' https://trusted-styles.com; " +
        "img-src 'self' https://trusted-images.com data:; " +
        "font-src 'self' https://trusted-fonts.com; " +
        "connect-src 'self' https://api.example.com;"
    );
    next();
});
                

Setting CSP via Meta Tag


<meta http-equiv="Content-Security-Policy" 
    content="default-src 'self'; script-src 'self' https://trusted-cdn.com">
                

CSP Directives

CSP provides several directives to control different types of resources:

Source Values

CSP directives accept various source values:

Using Nonces for Inline Scripts

Nonces provide a way to allow specific inline scripts while maintaining strong security:


// Server-side: Generate a random nonce for each request
const crypto = require('crypto');
app.use((req, res, next) => {
    const nonce = crypto.randomBytes(16).toString('base64');
    res.locals.cspNonce = nonce;
    
    res.setHeader('Content-Security-Policy', 
        `default-src 'self'; script-src 'self' 'nonce-${nonce}'`);
    next();
});

// In your template engine (e.g., EJS)
<script nonce="<%= cspNonce %>">
    // This inline script is allowed because it has the correct nonce
    console.log('CSP with nonce allows this script!');
</script>
                

CSP Reporting

CSP can also be configured to report violations without blocking content, which is useful during development and testing:

Setting Up CSP Reporting


// Report-only mode (doesn't block anything, just reports violations)
app.use((req, res, next) => {
    res.setHeader('Content-Security-Policy-Report-Only', 
        "default-src 'self'; " +
        "report-uri /csp-violation-report");
    next();
});

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

Real-World CSP Example: Google

Google uses a robust CSP on many of its services. For example, on Google Search, they use nonces for inline scripts, restrict connections to their own domains, and have a detailed reporting system for violations. This helps protect millions of users from potential XSS attacks, even if a vulnerability were to be found in their application code.

Implementing CORS and CSP Together

In a full-stack application, you'll typically implement both CORS on your backend API and CSP on your frontend application. Here's how they work together:

Express Backend with CORS and Helmet (for CSP)


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

// CORS configuration
app.use(cors({
    origin: ['https://app.example.com', 'https://admin.example.com'],
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true
}));

// Basic security headers including CSP
app.use(helmet());

// Custom CSP configuration
app.use(helmet.contentSecurityPolicy({
    directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", 'https://trusted-cdn.com'],
        styleSrc: ["'self'", 'https://trusted-styles.com'],
        imgSrc: ["'self'", 'data:', 'https://trusted-images.com'],
        connectSrc: ["'self'", 'https://api.example.com'],
        fontSrc: ["'self'", 'https://trusted-fonts.com'],
        objectSrc: ["'none'"],
        upgradeInsecureRequests: [],
        frameAncestors: ["'self'"]
    }
}));

// API routes
app.get('/api/data', (req, res) => {
    res.json({ message: 'This API is secured with CORS and CSP' });
});

// Static files
app.use(express.static('public'));

app.listen(3000, () => {
    console.log('Server running on port 3000');
});
                

React Frontend with CSP Considerations

When building frontend applications, especially with frameworks like React, you need to be careful with CSP implementation:

CSP Challenges with React

  • Inline styles: React often uses inline styles that may be blocked by CSP
    
    // This might be blocked by CSP
    <div style={{ color: 'red' }}>Dynamic style</div>
                            
  • eval() in development: Some bundlers use eval() for hot module replacement
    
    // CSP with webpack dev server needs:
    contentSecurityPolicy: {
        directives: {
            scriptSrc: ["'self'", "'unsafe-eval'"] // Only in development
        }
    }
                            

Solutions for React Apps


// Using style-loader in webpack config
module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            }
        ]
    }
};

// Using styled-components for CSS-in-JS
import styled from 'styled-components';

const RedDiv = styled.div`
    color: red;
`;

function App() {
    return <RedDiv>This works with strict CSP</RedDiv>;
}
                

Common CORS and CSP Issues

As you implement these security features, you'll likely encounter some common problems:

CORS Troubleshooting

Problem: Credentials Not Included


// Frontend Error:
// "Cross-Origin Request Blocked: The Same Origin Policy disallows 
// reading the remote resource at https://api.example.com/data. 
// (Reason: CORS header 'Access-Control-Allow-Origin' does not 
// match 'https://app.example.com')."

// Solution:
// Server needs to specify the exact origin:
res.header('Access-Control-Allow-Origin', 'https://app.example.com');
res.header('Access-Control-Allow-Credentials', 'true');

// And the client must include credentials:
fetch('https://api.example.com/data', {
    credentials: 'include' // Send cookies
});
                

Problem: Preflight Fails


// Options request gets 404 because the route isn't handling OPTIONS method

// Solution:
app.options('/api/data', cors()); // Respond to preflight for this route

// Or globally:
app.options('*', cors()); // Handle OPTIONS preflight for all routes
                

CSP Troubleshooting

Problem: Blocked Inline Scripts


// Console Error:
// "Refused to execute inline script because it violates the 
// following Content Security Policy directive: "script-src 'self'"."

// Solution:
// Use nonces for necessary inline scripts:
const nonce = crypto.randomBytes(16).toString('base64');

// In your CSP header:
`script-src 'self' 'nonce-${nonce}'`

// In your HTML:
<script nonce="${nonce}">
    // Your inline code here
</script>
                

Problem: Third-Party Resources Blocked


// Console Error:
// "Refused to load the image 'https://external-site.com/image.jpg' 
// because it violates the following CSP directive: "img-src 'self'"."

// Solution:
// Expand your CSP to include necessary resources:
`img-src 'self' https://external-site.com`
                

Debugging Tip: The Network Tab

When facing CORS or CSP issues, the browser's Developer Tools Network tab is your best friend. For CORS issues, look for the preflight OPTIONS request and check response headers. For CSP issues, the Console tab will show specific CSP violations with details about which directive was violated.

Best Practices

CORS Best Practices

CSP Best Practices

Complete Security Headers Setup


// Using Helmet in Express for comprehensive header security
const helmet = require('helmet');
const app = express();

app.use(helmet()); // Adds basic security headers

// Custom CSP configuration
app.use(
    helmet.contentSecurityPolicy({
        directives: {
            defaultSrc: ["'self'"],
            scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
            styleSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
            imgSrc: ["'self'", 'data:', 'https://trusted-images.com'],
            connectSrc: ["'self'", 'https://api.example.com'],
            fontSrc: ["'self'", 'https://fonts.gstatic.com'],
            objectSrc: ["'none'"],
            mediaSrc: ["'self'"],
            frameSrc: ["'self'"],
            frameAncestors: ["'self'"],
            formAction: ["'self'"],
            upgradeInsecureRequests: [],
            reportUri: '/csp-report'
        }
    })
);

// Generate nonce for each request
app.use((req, res, next) => {
    res.locals.nonce = crypto.randomBytes(16).toString('base64');
    next();
});
                

Practical Exercises

Exercise 1: CORS Configuration

Implement CORS in an Express.js application with the following requirements:

Exercise 2: Testing CORS

Create a simple HTML page that makes fetch requests to your API. Test the following scenarios:

Exercise 3: Implementing CSP

Implement a Content Security Policy for a web application with the following requirements:

Exercise 4: CSP Analysis

Use the browser's Developer Tools to analyze the CSP of three popular websites:

Additional Resources

Next Class Preparation

For our next session on Rate Limiting and DDOS Protection, please familiarize yourself with: