CORS Configuration with Claude Code: Origin Control and Preflight Optimization

Published: (March 11, 2026 at 12:56 AM EDT)
3 min read
Source: Dev.to

Source: Dev.to

CORS Configuration Rules

Security (required)

  • Never use Access-Control-Allow-Origin: * in production.
  • Load allowed origins from environment variables (no hard‑coding).
  • When using credentials, only allow specific origins (incompatible with *).
  • Only allow HTTP methods that are explicitly needed; e.g., DELETE must be listed explicitly.

Preflight

  • Register CORS middleware before all routes.
  • Return 204 for OPTIONS requests (no response body).
  • Cache preflight responses for 24 hours (max‑age=86400).

Headers

  • Request headers to allow: Content-Type, Authorization, X-Request-ID.
  • Response headers to expose: X-Total-Count, X-Request-ID.

Generated CORS configuration

src/middleware/cors.ts

// src/middleware/cors.ts
import cors from 'cors';

const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(',').map(o => o.trim()) ?? [];

export const corsMiddleware = cors({
  origin: (origin, callback) => {
    // Same‑origin or server‑to‑server requests have undefined origin
    if (!origin) return callback(null, true);

    if (ALLOWED_ORIGINS.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error(`CORS: origin ${origin} not allowed`));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
  exposedHeaders: ['X-Total-Count', 'X-Request-ID'],
  maxAge: 86400,
});

src/app.ts

// src/app.ts
import express from 'express';
import { corsMiddleware } from './middleware/cors';

const app = express();

// CORS must be registered before all routes
app.use(corsMiddleware);

// Return 204 for OPTIONS preflight
app.options('*', corsMiddleware, (req, res) => {
  res.status(204).end();
});

app.use(express.json());

// ...other route handlers

Multi‑tenant CORS

src/middleware/dynamicCors.ts

// src/middleware/dynamicCors.ts
import { Request, Response, NextFunction } from 'express';
import redis from './redis';   // assume a configured Redis client
import prisma from './prisma'; // assume a configured Prisma client

async function getAllowedOrigins(tenantId: string): Promise {
  const cacheKey = `cors:tenant:${tenantId}`;

  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { allowedOrigins: true },
  });

  const origins = tenant?.allowedOrigins ?? [];
  await redis.set(cacheKey, JSON.stringify(origins), { EX: 300 }); // 5 min TTL

  return origins;
}

export const dynamicCorsMiddleware = async (req: Request, res: Response, next: NextFunction) => {
  const tenantId = req.headers['x-tenant-id'] as string;
  const origin = req.headers.origin as string;

  if (!tenantId || !origin) return next();

  const allowedOrigins = await getAllowedOrigins(tenantId);

  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Vary', 'Origin'); // Prevent CDN/proxy cache pollution
  }

  next();
};

.env.example

# Comma‑separated list of allowed origins for production
ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com

# Development (uncomment as needed)
# ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173

Design notes

  • CLAUDE.md: Enforce “no * in production”, load origins from environment, and note the credentials restriction.
  • Origin validation: Implemented as a dynamic allow‑list check; no hard‑coded values.
  • Vary header: Added to avoid CDN/proxy cache pollution when origins differ per request.
  • Preflight cache: maxAge: 86400 reduces unnecessary OPTIONS round‑trips.

For further reading on CORS security checks, see the Security Pack (¥1,480) which includes /security-check for detecting wildcard origins, credential leaks, and missing Vary headers.

Myouga (@myougatheaxo) – Claude Code engineer focused on API security.

0 views
Back to Blog

Related posts

Read more »

Cloudflare crawl endpoint

Article URL: https://developers.cloudflare.com/changelog/post/2026-03-10-br-crawl-endpoint/ Comments URL: https://news.ycombinator.com/item?id=47329557 Points:...