CORS Configuration

Configure Cross-Origin Resource Sharing (CORS) in MoroJS to enable secure cross-domain requests while maintaining proper security boundaries.

Basic CORS Setup

MoroJS provides built-in CORS support with intelligent defaults and comprehensive configuration options for production environments.

Simple CORS Configuration

typescript

1import { createApp } from '@morojs/moro';
2
3// Enable CORS with default settings (allows all origins in development)
4const app = createApp({
5  cors: true,
6  compression: true,
7  helmet: true
8});
9
10// The actual MoroJS implementation uses simple boolean flag
11// CORS is handled automatically by the framework
12
13// Example from actual implementation
14const app = createApp({
15  cors: true, // This enables CORS with sensible defaults
16  compression: true,
17  helmet: true
18});
19
20// Routes automatically inherit CORS settings
21app.get('/users', (req, res) => {
22  return { success: true, data: [] };
23});
24
25app.post('/users', (req, res) => {
26  return { success: true, message: 'User created' };
27});

Default Behavior

When CORS is enabled, MoroJS automatically:

  • • Handles preflight OPTIONS requests
  • • Sets appropriate CORS headers
  • • Validates origin against allowed list
  • • Manages credential handling
  • • Optimizes for performance

Advanced Configuration

Comprehensive CORS Configuration

typescript

1const app = createApp({
2  security: {
3    cors: {
4      // Origin configuration
5      origin: [
6        'https://app.example.com',
7        'https://admin.example.com',
8        'https://mobile.example.com'
9      ],
10      
11      // Or dynamic origin validation
12      origin: (origin, callback) => {
13        // Allow requests with no origin (mobile apps, etc.)
14        if (!origin) return callback(null, true);
15        
16        // Check against allowed domains
17        const allowedDomains = [
18          'example.com',
19          'staging.example.com',
20          'localhost'
21        ];
22        
23        const hostname = new URL(origin).hostname;
24        const isAllowed = allowedDomains.some(domain => 
25          hostname === domain || hostname.endsWith('.' + domain)
26        );
27        
28        callback(null, isAllowed);
29      },
30      
31      // HTTP methods
32      methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
33      
34      // Allowed headers
35      allowedHeaders: [
36        'Content-Type',
37        'Authorization',
38        'X-Requested-With',
39        'X-API-Key',
40        'X-Client-Version'
41      ],
42      
43      // Exposed headers (available to client)
44      exposedHeaders: [
45        'X-Total-Count',
46        'X-Page-Count',
47        'X-Rate-Limit-Remaining'
48      ],
49      
50      // Credentials support
51      credentials: true,
52      
53      // Preflight cache duration (in seconds)
54      maxAge: 86400, // 24 hours
55      
56      // Continue to next middleware on successful preflight
57      preflightContinue: false,
58      
59      // Status code for successful OPTIONS requests
60      optionsSuccessStatus: 204
61    }
62  }
63});

Environment-Specific CORS

Environment-Based Configuration

typescript

1const getCorsConfig = () => {
2  const environment = process.env.NODE_ENV;
3  
4  switch (environment) {
5    case 'development':
6      return {
7        origin: true, // Allow all origins
8        credentials: true,
9        optionsSuccessStatus: 200
10      };
11      
12    case 'staging':
13      return {
14        origin: [
15          'https://staging.example.com',
16          'https://staging-admin.example.com',
17          /^https:\/\/.*\.vercel\.app$/
18        ],
19        credentials: true,
20        maxAge: 3600 // 1 hour
21      };
22      
23    case 'production':
24      return {
25        origin: [
26          'https://app.example.com',
27          'https://admin.example.com'
28        ],
29        credentials: true,
30        maxAge: 86400, // 24 hours
31        
32        // Strict security in production
33        allowedHeaders: [
34          'Content-Type',
35          'Authorization'
36        ],
37        methods: ['GET', 'POST', 'PUT', 'DELETE']
38      };
39      
40    default:
41      return false; // Disable CORS
42  }
43};
44
45const app = createApp({
46  security: {
47    cors: getCorsConfig()
48  }
49});

Dynamic CORS with Database

typescript

1// Store allowed origins in database
2const getAllowedOrigins = async () => {
3  const origins = await db.query('SELECT origin FROM allowed_origins WHERE active = true');
4  return origins.map(row => row.origin);
5};
6
7const app = createApp({
8  security: {
9    cors: {
10      origin: async (origin, callback) => {
11        try {
12          const allowedOrigins = await getAllowedOrigins();
13          const isAllowed = allowedOrigins.includes(origin);
14          callback(null, isAllowed);
15        } catch (error) {
16          callback(error, false);
17        }
18      },
19      credentials: true
20    }
21  }
22});

Route-Specific CORS

Per-Route CORS Configuration

typescript

1import { cors } from '@morojs/moro/middleware';
2
3// Public API - Allow all origins
4app.get('/api/public/data', {
5  middleware: [
6    cors({
7      origin: '*',
8      methods: ['GET'],
9      credentials: false
10    })
11  ],
12  handler: () => ({ message: 'Public data' })
13});
14
15// Admin API - Restricted origins
16app.post('/api/admin/users', {
17  middleware: [
18    cors({
19      origin: ['https://admin.example.com'],
20      methods: ['POST'],
21      credentials: true,
22      allowedHeaders: ['Content-Type', 'Authorization', 'X-Admin-Token']
23    }),
24    requireAdminAuth
25  ],
26  handler: createUser
27});
28
29// Webhook endpoint - No CORS needed
30app.post('/webhooks/stripe', {
31  middleware: [
32    cors(false) // Disable CORS for this route
33  ],
34  handler: handleStripeWebhook
35});
36
37// API with subdomain support
38app.get('/api/data', {
39  middleware: [
40    cors({
41      origin: (origin, callback) => {
42        if (!origin) return callback(null, true);
43        
44        // Allow any subdomain of example.com
45        const isSubdomain = /^https://.*.example.com$/.test(origin);
46        callback(null, isSubdomain);
47      },
48      credentials: true
49    })
50  ],
51  handler: getData
52});

Troubleshooting CORS

Common Issues

  • • Origin not in allowed list
  • • Missing credentials configuration
  • • Incorrect preflight handling
  • • Wrong HTTP method allowed
  • • Missing required headers
  • • Browser cache issues

Best Practices

  • • Use specific origins in production
  • • Enable credentials only when needed
  • • Set appropriate cache duration
  • • Minimize allowed headers
  • • Log CORS rejections
  • • Test with different browsers

CORS Debugging Middleware

typescript

1// Debug CORS issues in development
2const corsDebugger = (req, res, next) => {
3  if (process.env.NODE_ENV === 'development') {
4    console.log('CORS Debug Info:', {
5      origin: req.headers.origin,
6      method: req.method,
7      headers: req.headers,
8      isPreFlight: req.method === 'OPTIONS'
9    });
10    
11    // Log response headers
12    const originalSend = res.send;
13    res.send = function(data) {
14      console.log('CORS Response Headers:', {
15        'access-control-allow-origin': res.get('Access-Control-Allow-Origin'),
16        'access-control-allow-methods': res.get('Access-Control-Allow-Methods'),
17        'access-control-allow-headers': res.get('Access-Control-Allow-Headers'),
18        'access-control-allow-credentials': res.get('Access-Control-Allow-Credentials')
19      });
20      return originalSend.call(this, data);
21    };
22  }
23  next();
24};
25
26// Use before CORS middleware
27app.use(corsDebugger);
28app.use(cors(corsConfig));

CORS Testing Utility

typescript

1// Utility function to test CORS configuration
2const testCorsOrigin = async (origin, endpoint = '/api/test') => {
3  try {
4    const response = await fetch(`${process.env.API_URL}${endpoint}`, {
5      method: 'OPTIONS',
6      headers: {
7        'Origin': origin,
8        'Access-Control-Request-Method': 'GET',
9        'Access-Control-Request-Headers': 'Content-Type'
10      }
11    });
12    
13    const corsHeaders = {
14      allowOrigin: response.headers.get('Access-Control-Allow-Origin'),
15      allowMethods: response.headers.get('Access-Control-Allow-Methods'),
16      allowHeaders: response.headers.get('Access-Control-Allow-Headers'),
17      allowCredentials: response.headers.get('Access-Control-Allow-Credentials')
18    };
19    
20    return {
21      allowed: response.ok,
22      status: response.status,
23      headers: corsHeaders
24    };
25  } catch (error) {
26    return {
27      allowed: false,
28      error: error.message
29    };
30  }
31};
32
33// Test different origins
34const testOrigins = [
35  'https://app.example.com',
36  'https://malicious.com',
37  'http://localhost:3000'
38];
39
40testOrigins.forEach(async (origin) => {
41  const result = await testCorsOrigin(origin);
42  console.log(`Origin ${origin}:`, result);
43});

Next Steps