Skip to main content

Command Palette

Search for a command to run...

CORS Configuration: Handle Cross-Origin Requests

Learn: CORS Configuration: Handle Cross-Origin Requests

Updated
5 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

CORS Configuration: Handle Cross-Origin Requests & Security Headers

Problem

Modern web applications often need to serve requests from different origins (domains, protocols, ports). Without proper CORS (Cross-Origin Resource Sharing) configuration, browsers block these requests for security reasons. Additionally, missing security headers expose applications to various attacks like XSS, clickjacking, and data theft.

Key Issues:

  • Browser blocks cross-origin requests by default
  • Sensitive data exposed without proper headers
  • Vulnerable to XSS, clickjacking, and MIME-type sniffing attacks
  • No protection against credential theft or cache poisoning

Solution

Implement a comprehensive CORS and security headers strategy:

  1. Configure CORS properly - Allow specific origins, methods, and credentials
  2. Add security headers - Implement CSP, X-Frame-Options, HSTS, etc.
  3. Validate requests - Check origin, method, and headers
  4. Handle preflight requests - Respond to OPTIONS requests correctly
  5. Environment-based configuration - Different rules for dev/prod

Code Implementation

1. Express.js with CORS Middleware

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

// Security headers with Helmet
app.use(helmet());

// Custom CORS configuration
const corsOptions = {
  origin: function (origin, callback) {
    const allowedOrigins = [
      'https://example.com',
      'https://app.example.com',
      'http://localhost:3000' // Development
    ];

    // Allow requests with no origin (like mobile apps or curl requests)
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true, // Allow cookies/auth headers
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
  exposedHeaders: ['X-Total-Count', 'X-Page-Number'],
  maxAge: 86400 // 24 hours
};

app.use(cors(corsOptions));

// Additional security headers
app.use((req, res, next) => {
  // Content Security Policy
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:"
  );

  // Prevent clickjacking
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');

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

  // Enable XSS protection
  res.setHeader('X-XSS-Protection', '1; mode=block');

  // Referrer Policy
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // Permissions Policy (formerly Feature Policy)
  res.setHeader(
    'Permissions-Policy',
    'geolocation=(), microphone=(), camera=()'
  );

  next();
});

// Handle preflight requests
app.options('*', cors(corsOptions));

// API Routes
app.get('/api/data', (req, res) => {
  res.json({ message: 'CORS enabled data', data: [] });
});

app.post('/api/data', (req, res) => {
  res.json({ message: 'Data received', status: 'success' });
});

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

2. Advanced CORS with Dynamic Origin Validation

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

class CORSManager {
  constructor() {
    this.allowedOrigins = new Set([
      'https://example.com',
      'https://app.example.com'
    ]);
    this.trustedDomains = ['example.com'];
  }

  isOriginAllowed(origin) {
    if (!origin) return true; // Allow non-browser requests

    // Check exact match
    if (this.allowedOrigins.has(origin)) return true;

    // Check domain pattern
    try {
      const url = new URL(origin);
      return this.trustedDomains.some(domain => 
        url.hostname.endsWith(domain)
      );
    } catch {
      return false;
    }
  }

  addOrigin(origin) {
    this.allowedOrigins.add(origin);
  }

  removeOrigin(origin) {
    this.allowedOrigins.delete(origin);
  }

  getMiddleware() {
    return (req, res, next) => {
      const origin = req.headers.origin;

      if (this.isOriginAllowed(origin)) {
        res.setHeader('Access-Control-Allow-Origin', origin || '*');
        res.setHeader('Access-Control-Allow-Credentials', 'true');
        res.setHeader(
          'Access-Control-Allow-Methods',
          'GET, POST, PUT, DELETE, PATCH, OPTIONS'
        );
        res.setHeader(
          'Access-Control-Allow-Headers',
          'Content-Type, Authorization, X-Requested-With'
        );
        res.setHeader('Access-Control-Max-Age', '86400');
      }

      // Handle preflight
      if (req.method === 'OPTIONS') {
        return res.sendStatus(200);
      }

      next();
    };
  }
}

const corsManager = new CORSManager();
app.use(corsManager.getMiddleware());

app.get('/api/secure', (req, res) => {
  res.json({ data: 'Secure data' });
});

app.listen(3000);

3. Nginx Configuration

# nginx.conf

server {
    listen 80;
    server_name api.example.com;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # CORS handling
    set $cors_origin "";
    set $cors_cred "";
    set $cors_methods "";
    set $cors_headers "";

    if ($http_origin ~* ^(https?://(example\.com|app\.example\.com|localhost:3000))$) {
        set $cors_origin $http_origin;
        set $cors_cred "true";
        set $cors_methods "GET, POST, PUT, DELETE, OPTIONS";
        set $cors_headers "Content-Type, Authorization";
    }

    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Credentials $cors_cred always;
    add_header Access-Control-Allow-Methods $cors_methods always;
    add_header Access-Control-Allow-Headers $cors_headers always;
    add_header Access-Control-Max-Age "86400" always;

    # Handle preflight requests
    if ($request_method = 'OPTIONS') {
        return 204;
    }

    location /api/ {
        proxy_pass http://backend:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

4. Environment-Based Configuration

// config/cors.js
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';

const corsConfig = {
  development: {
    origin: ['http://localhost:3000', 'http://localhost:3001', 'http://127.0.0.1:3000'],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    maxAge: 3600
  },
  production: {
    origin: [
      'https://example.com',
      'https://app.example.com',
      'https://www.example.com'
    ],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    maxAge: 86400
  },
  staging: {
    origin: [
      'https://staging.example.com',
      'https://staging-app.example.com'
    ],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    maxAge: 86400
  }
};

module.exports = corsConfig[process.env.NODE_ENV] || corsConfig.development;

5. Client-Side Request Handling

// Client code
async function fetchWithCORS(url, options = {}) {
  try {
    const response = await fetch(url, {
      method: options.method || 'GET',
      credentials: 'include', // Include cookies
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${getToken()}`,
        ...options.headers
      },
      body: options.body ? JSON.stringify(options.body) : undefined
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error('CORS request failed:', error);
    throw error;
  }
}

// Usage
fetchWithCORS('https://api.example.com/data', {
  method: 'POST',
  body: { name: 'John' }
}).then(data => console.log(data));

6. Testing CORS Configuration

// test/cors.test.js
const request = require('supertest');
const app = require('../app');

describe('CORS Configuration', () => {
  test('should allow requests from allowed origin', async () => {
    const response = await request(app)
      .get('/api/data')
      .set('Origin', 'https://example.com');

    expect(response.headers['access-control-allow-origin']).toBe('https://example.com');
    expect(response.status).toBe(200);
  });

  test('should reject requests from disallowed origin', async () => {
    const response = await request(app)
      .get('/api/data')
      .set('Origin', 'https://malicious.com');

    expect(response.headers['access-control-allow-origin']).toBeUndefined();
  });

  test('should handle preflight requests', async () => {
    const response = await request(app)
      .options('/api/data')
      .set('Origin', 'https://example.com')
      .set('Access-Control-Request-Method', 'POST');

    expect(response.status).toBe(200);
    expect(response.headers['access-control-allow-methods']).toContain('POST');
  });

  test('should include security headers', async () => {
    const response = await request(app).get('/api/data');

    expect(response.headers['x-frame-options']).toBe('SAMEORIGIN');
    expect(response.headers['x-content-type-options']).toBe('nosniff');
    expect(response.headers['content-security-policy']).toBeDefined();
  });
});

Key Security Headers Explained

HeaderPurposeExample
CORS HeadersControl cross-origin accessAccess-Control-Allow-Origin
CSPPrevent XSS attacksContent-Security-Policy
X-Frame-OptionsPrevent clickjackingSAMEORIGIN
HSTSForce HTTPSStrict-Transport-Security
X-Content-Type-OptionsPrevent MIME sniffingnosniff
Referrer-PolicyControl referrer infostrict-origin-when-cross-origin

Best Practices

✅ Whitelist specific origins instead of using *
✅ Use HTTPS in production
✅ Implement HSTS for security
✅ Validate all incoming requests
✅ Use environment-specific configurations
✅ Log CORS rejections for monitoring
✅ Regularly audit allowed origins
✅ Keep security headers updated