Skip to main content

What is CORS?

Cross-Origin Resource Sharing (CORS) is a security feature implemented by web browsers that restricts web pages from making requests to a different domain than the one serving the web page. This is known as the “same-origin policy.”
CORS is essential when your frontend application (e.g., running on http://localhost:5173) needs to communicate with your API (running on http://localhost:3000).

Why CORS Matters

  • Browser Security: Prevents malicious websites from accessing your API
  • Controlled Access: Allows you to specify which origins can access your resources
  • Production Safety: Protects your API from unauthorized cross-origin requests
Without proper CORS configuration, browsers will block API requests from your frontend, resulting in errors like:
Access to fetch at 'http://localhost:3000/users/upload' from origin 'http://localhost:5173' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.

Default CORS Configuration

API Master comes with a pre-configured CORS setup. Here’s the actual implementation from app.ts:
app.ts
import express from 'express';
import cors from 'cors';
import path from 'path';
import userRoutes from './src/routes/userRoutes';

const app = express();

// CORS Configuration
const corsOptions = {
  origin: '*', // Permitir todas las origines (para desarrollo; en producción, especifica dominios)
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
};

// Middleware
app.use(cors(corsOptions));
app.use(express.json());
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

Configuration Breakdown

What it does: Allows requests from any originUse case: Perfect for development and testingSecurity: ⚠️ Not recommended for productionThe wildcard '*' means any website can make requests to your API. This is convenient during development but poses security risks in production.
What it does: Specifies which HTTP methods are allowed for cross-origin requestsAllowed operations:
  • GET - Retrieve data
  • POST - Create resources (e.g., file upload)
  • PUT - Update resources
  • DELETE - Remove resources
If a client tries to use an unlisted method (e.g., PATCH), the browser will block the request.
What it does: Defines which HTTP headers can be used in cross-origin requestsCommon headers:
  • Content-Type - Required for JSON and file uploads
  • Authorization - For authentication tokens (JWT, Bearer tokens)
If your client sends a custom header not in this list, the request will be rejected.

Production Configuration

For production environments, you should restrict access to specific domains. Here’s how to configure secure CORS:
app.ts
const corsOptions = {
  origin: 'https://yourdomain.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
};

app.use(cors(corsOptions));
Only requests from https://yourdomain.com will be allowed.
Never use origin: '*' with credentials: true. Browsers will reject this configuration as it’s a security violation.

Credentials Support

If your API needs to work with cookies, authentication tokens, or HTTP authentication:
const corsOptions = {
  origin: 'https://yourdomain.com', // Must be specific, not '*'
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true // Enable credentials
};

app.use(cors(corsOptions));
On the client side, you must also enable credentials:
fetch('http://localhost:3000/users/upload', {
  method: 'POST',
  credentials: 'include', // Important!
  body: formData
});

Development vs Production Setup

1

Development

Use permissive CORS for easier testing:
const corsOptions = {
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
};
2

Staging

Restrict to staging domain:
const corsOptions = {
  origin: 'https://staging.yourdomain.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
};
3

Production

Lock down to production domain(s):
const corsOptions = {
  origin: ['https://yourdomain.com', 'https://www.yourdomain.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
};

Advanced Configuration

Additional Headers

If you need to support custom headers:
const corsOptions = {
  origin: 'https://yourdomain.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: [
    'Content-Type', 
    'Authorization',
    'X-Requested-With',
    'X-Custom-Header'
  ],
  exposedHeaders: ['X-Total-Count', 'X-Page-Number'], // Headers accessible to client
  credentials: true,
  maxAge: 86400 // Cache preflight requests for 24 hours
};

Preflighted Requests

For complex requests (e.g., with custom headers), browsers send a preflight OPTIONS request:
const corsOptions = {
  origin: 'https://yourdomain.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  preflightContinue: false,
  optionsSuccessStatus: 204 // Some legacy browsers choke on 204
};
Set maxAge to cache preflight requests and reduce the number of OPTIONS requests to your server.

Troubleshooting CORS Issues

Common Errors and Solutions

Problem: CORS middleware not applied or origin not allowedSolutions:
  1. Verify app.use(cors(corsOptions)) is before your routes
  2. Check if your origin is in the allowed list
  3. Ensure the API server is running
// Correct order
app.use(cors(corsOptions));
app.use(express.json());
app.use('/users', userRoutes); // Routes come after CORS
Problem: Using origin: '*' with credentials: trueSolution: Specify exact origin(s):
const corsOptions = {
  origin: 'https://yourdomain.com', // Not '*'
  credentials: true
};
Problem: HTTP method not in allowed methods listSolution: Add the method to your configuration:
const corsOptions = {
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], // Add PATCH
  allowedHeaders: ['Content-Type', 'Authorization']
};
Problem: Client sending headers not in allowedHeadersSolution: Add required headers:
const corsOptions = {
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: [
    'Content-Type', 
    'Authorization',
    'X-Custom-Header' // Add your custom header
  ]
};

Testing CORS Configuration

Test your CORS setup with cURL:
# Test preflight request
curl -X OPTIONS http://localhost:3000/users/upload \
  -H "Origin: https://yourdomain.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type" \
  -v

# Look for these headers in response:
# Access-Control-Allow-Origin: https://yourdomain.com
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# Access-Control-Allow-Headers: Content-Type, Authorization

Best Practices

1

Never use wildcards in production

Always specify exact origins for production deployments.
2

Use environment variables

Store allowed origins in environment variables for easy configuration across environments.
3

Enable credentials only when needed

Only set credentials: true if you’re using cookies or HTTP authentication.
4

Set appropriate maxAge

Cache preflight requests to reduce server load:
maxAge: 86400 // 24 hours
5

Log CORS rejections

Add logging to debug CORS issues:
const corsOptions = {
  origin: (origin, callback) => {
    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      console.log(`CORS blocked request from origin: ${origin}`);
      callback(new Error('Not allowed by CORS'));
    }
  }
};
CORS is a browser security feature. Tools like Postman, cURL, and server-to-server requests bypass CORS entirely.

Build docs developers (and LLMs) love