← Back to list

CORS in Production: Implementation, Debugging, and Security Best Practices

Most developers first encounter CORS when they see a cryptic browser error:

Navidbarsalari · 2026-06-07 13:46 · 3 claps · 4.2 min read
#security #backend-development #cors #backend-engineering #software-development
Open on Medium ↗
Wiki topics: 💻 · Programming 🌐 · Web Development

CORS in Production: Implementation, Debugging, and Security Best Practices

Most developers first encounter CORS when they see a cryptic browser error:

Access to fetch at ‘https://api.example.com' from origin ‘https://frontend.com' has been blocked by CORS policy.

The common reaction is to search for a quick fix, add cors() middleware, and move on.

Unfortunately, that approach often creates security vulnerabilities or leads to configuration problems later in production.

In this article, we’ll explore how to properly implement CORS in backend applications, troubleshoot common issues, and follow security best practices that scale to enterprise systems and microservice architectures.

Implementing CORS Correctly

NestJS

NestJS provides built-in CORS support through the underlying Express or Fastify adapter.

A basic setup looks like this:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors();
  await app.listen(3000);
}

While convenient during development, production systems should use explicit configuration:

app.enableCors({
  origin: [
    'https://frontend.com',
    'https://app.frontend.com'
  ],
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 3600
});

This configuration ensures that only trusted frontend applications can access the API.

Dynamic Origin Validation

In many SaaS applications, allowed origins may vary by tenant or environment.

app.enableCors({
  origin: (origin, callback) => {
    const allowedOrigins = [
      'https://frontend.com',
      /\.example\.com$/
    ];
if (
      !origin ||
      allowedOrigins.some(item =>
        typeof item === 'string'
          ? item === origin
          : item.test(origin)
      )
    ) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true
});

This approach provides flexibility without sacrificing security.

Express.js Configuration

Express applications commonly rely on the cors package.

Allowing a Single Origin

const cors = require('cors');
app.use(cors({
  origin: 'https://frontend.com',
  credentials: true
}));

Multiple Allowed Origins

const allowedOrigins = [
  'https://frontend.com',
  'https://app.frontend.com'
];

app.use(cors({
  origin: function(origin, callback) {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true
}));

Route-Specific Policies

Public endpoints and private endpoints often require different rules.

app.get('/public', cors(), handler);

app.get('/private', cors({
  origin: 'https://frontend.com',
  credentials: true
}), handler);

This allows finer control over API exposure.

Django Configuration

Using django-cors-headers:

INSTALLED_APPS = [
    'corsheaders',
]
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
]

Production configuration:

CORS_ALLOWED_ORIGINS = [
    "https://frontend.com",
    "https://app.frontend.com",
]
CORS_ALLOW_CREDENTIALS = True

Restricting origins is strongly preferred over:

CORS_ALLOW_ALL_ORIGINS = True

which should only be used temporarily during development.

Laravel Configuration

Laravel includes native CORS support through the framework configuration.

return [
'paths' => ['api/*'],
    'allowed_methods' => ['*'],
    'allowed_origins' => [
        'https://frontend.com',
        'https://app.frontend.com',
    ],
    'allowed_headers' => ['*'],
    'supports_credentials' => true,
];

For production workloads, it’s recommended to explicitly define methods and headers whenever possible.

Common CORS Errors and How to Fix Them

1. No Access-Control-Allow-Origin Header

Error:

No 'Access-Control-Allow-Origin' header is present on the requested resource

Cause:

The server never returns a valid CORS response.

Solution:

app.use(cors({
  origin: 'https://frontend.com'
}));

Verify the response contains:

Access-Control-Allow-Origin: https://frontend.com

2. Credentials with Wildcard Origin

Incorrect configuration:

app.use(cors({
  origin: '*',
  credentials: true
}));

Browser error:

The value of the Access-Control-Allow-Origin header
must not be the wildcard '*' when credentials mode is include.

Correct configuration:

app.use(cors({
  origin: 'https://frontend.com',
  credentials: true
}));

Browsers intentionally block wildcard origins when cookies or authentication credentials are involved.

3. Preflight OPTIONS Request Returns 403

A surprisingly common issue.

Incorrect middleware order:

app.use(authMiddleware);
app.use(cors());

In this setup, authentication intercepts the OPTIONS request before CORS can process it.

Correct order:

app.use(cors());
app.use(authMiddleware);

Alternatively:

app.use((req, res, next) => {
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
next();
});

This allows preflight requests to succeed without authentication.

4. Localhost Development Problems

Developers often assume these origins are identical:

http://localhost:3000
http://localhost:4000

They are not.

Different ports create different origins.

Backend configuration must explicitly allow the frontend:

app.use(cors({
  origin: 'http://localhost:3000'
}));

5. Custom Header Rejected

Frontend:

fetch('/users', {
  headers: {
    'X-Request-ID': '123'
  }
});

Error:

Request header field X-Request-ID is not allowed

Backend fix:

app.use(cors({
  allowedHeaders: [
    'Content-Type',
    'Authorization',
    'X-Request-ID'
  ]
}));

Security Best Practices

CORS misconfigurations are among the most common API security weaknesses.

Here are the practices every team should adopt.

Never Use Wildcards for Sensitive APIs

Avoid:

origin: '*'

Especially when authentication is involved.

A wildcard effectively grants access to every website on the internet.

Maintain a Strict Allowlist

Good:

const allowedOrigins = [
  'https://frontend.com',
  'https://app.frontend.com'
];

Bad:

origin: true

Explicit is safer than implicit.

Enable Preflight Caching

maxAge: 86400

Benefits:

  • Fewer OPTIONS requests
  • Reduced latency
  • Lower server load

Large applications often see dramatic performance improvements from proper caching.

Restrict HTTP Methods

Prefer:

methods: [
  'GET',
  'POST',
  'PUT',
  'DELETE'
]

Instead of:

methods: '*'

Only expose functionality that is actually needed.

Enforce HTTPS

Production traffic should never rely on insecure origins.

Example:

if (
  process.env.NODE_ENV === 'production' &&
  origin &&
  !origin.startsWith('https://')
) {
  return callback(new Error('HTTPS required'));
}

This prevents accidental exposure through unsecured channels.

CORS vs Development Proxies

During development, configuring backend CORS policies may be inconvenient.

Frontend teams often use proxies.

Vite Example

export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        rewrite: path =>
          path.replace(/^\/api/, '')
      }
    }
  }
};

Frontend code becomes:

fetch('/api/users');

The development server forwards requests behind the scenes.

Advantages

  • No backend changes
  • Faster local setup
  • Easier frontend development

Limitations

  • Development-only solution
  • Not a replacement for production CORS policies

CORS in Microservice Architectures

Modern systems rarely consist of a single backend service.

A common architecture looks like this:

Frontend
    │
    ▼
API Gateway
    │
 ┌──┼──────────────┐
 │  │      │       │
 ▼  ▼      ▼       ▼
Auth User Order Payment

In this setup:

  • Frontend communicates with the API Gateway
  • Gateway enforces CORS
  • Internal services communicate privately

Only one layer needs CORS configuration.

Benefits include:

  • Centralized security
  • Easier maintenance
  • Consistent policies across services

This pattern is widely used in large-scale systems.

Testing CORS

Never assume CORS works correctly.

Test it.

Manual Testing with cURL

Preflight test:

curl -X OPTIONS https://api.example.com/users \
  -H "Origin: https://frontend.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Authorization" \
  -v

Verify:

Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers

are present and correct.

Automated Testing

Example using Jest and Supertest:

it('allows requests from approved origins', () => {
  return request(app.getHttpServer())
    .get('/users')
    .set('Origin', 'https://frontend.com')
    .expect(200)
    .expect(
      'Access-Control-Allow-Origin',
      'https://frontend.com'
    );
});

Automated tests help prevent regressions during deployments and infrastructure changes.

Final Thoughts

CORS is often misunderstood as an annoying browser restriction, but it is actually one of the web platform’s most important security mechanisms.

A production-grade CORS strategy should include:

  • Explicit origin allowlists
  • Proper credential handling
  • Secure preflight processing
  • HTTPS enforcement
  • Automated testing
  • Centralized management through API gateways

When configured correctly, CORS provides a strong balance between security and interoperability, enabling modern web applications to communicate safely across origins without exposing users to cross-site attacks.


메타데이터
post_id
01ea0389a0a8
slug
cors-in-production-implementation-debugging-and-security-best-practices-01ea0389a0a8
url
https://medium.com/@navidbarsalari/cors-in-production-implementation-debugging-and-security-best-practices-01ea0389a0a8
canonical_url
https://medium.com/@navidbarsalari/cors-in-production-implementation-debugging-and-security-best-practices-01ea0389a0a8
author_url
https://medium.com/@navidbarsalari
status
ok
fetched_at
2026-06-15 20:49:13