DEV Community

myougaTheAxo
myougaTheAxo

Posted on

CORS Configuration with Claude Code: Origin Control and Preflight Optimization

Misconfigured CORS is a security hole — Access-Control-Allow-Origin: * in production lets any site call your API. Claude Code generates safe CORS configuration from CLAUDE.md rules.


CLAUDE.md for CORS Rules

## CORS Configuration Rules

### Security (required)
- Never use `Access-Control-Allow-Origin: *` in production
- Load allowed origins from env variables (no hardcoding)
- When using credentials, only allow specific origins (incompatible with *)
- Only allow methods explicitly needed (DELETE etc. must be explicit)

### Preflight
- Register CORS middleware before all routes
- Return 204 for OPTIONS requests (no body)
- Preflight cache: max-age=86400 (24 hours)

### Headers
- Request: Content-Type, Authorization, X-Request-ID
- Response: X-Total-Count, X-Request-ID (expose custom headers)
Enter fullscreen mode Exit fullscreen mode

Generating CORS Configuration

Generate CORS configuration.

Requirements:
- Switch allowed origins between production/development
- Support credentials (cookie auth)
- 24-hour preflight cache
- Expose custom response header X-Total-Count

Save to: src/middleware/cors.ts
Enter fullscreen mode Exit fullscreen mode

Generated:

// 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 requests (server-to-server, curl) 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,
});
Enter fullscreen mode Exit fullscreen mode
// 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());
Enter fullscreen mode Exit fullscreen mode

Dynamic Origin Validation (Multi-tenant)

Generate multi-tenant CORS where each tenant has different allowed origins.

Requirements:
- Identify tenant from subdomain or header
- Fetch allowed origins from DB per tenant
- Cache results in Redis (TTL 5 minutes)
Enter fullscreen mode Exit fullscreen mode
// src/middleware/dynamicCors.ts
async function getAllowedOrigins(tenantId: string): Promise<string[]> {
  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 });

  return origins;
}

export const dynamicCorsMiddleware = async (req, res, next) => {
  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 cache pollution
  }

  next();
};
Enter fullscreen mode Exit fullscreen mode

Environment Variables

# .env.example
# Comma-separated list of allowed origins
ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com

# Development
# ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
Enter fullscreen mode Exit fullscreen mode

Summary

Design CORS configuration with Claude Code:

  1. CLAUDE.md — No * in production, origins from env, note credentials restriction
  2. Origin validation function — Dynamic allow-list check (no hardcoding)
  3. Vary header — Prevent CDN/proxy cache pollution
  4. Preflight cache — Reduce unnecessary OPTIONS round-trips

Security Pack (¥1,480) includes /security-check for CORS misconfigurations — wildcard origins, credentials leaks, missing Vary headers.

👉 prompt-works.jp

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

Top comments (0)