CORS - Cross-Origin Resource Sharing Middleware

This middleware provides comprehensive CORS support for Wings applications, handling both preflight OPTIONS requests and regular request CORS headers. It follows the CORS specification (RFC 6454) and WHATWG CORS standard to ensure proper cross-origin request handling.

CORS is a security mechanism that uses HTTP headers to tell browsers which origins are allowed to access a web API from client-side JavaScript. Without proper CORS configuration, browsers will block cross-origin requests to protect users from malicious scripts.

The middleware handles two types of requests:

  1. Simple Requests: GET, POST with standard headers - just need response headers
  2. Preflight Requests: Complex requests that require an OPTIONS preflight check

The origin option supports multiple configuration patterns for maximum flexibility:

  • String: Exact origin match ('https://myapp.com')
  • Array: Multiple allowed origins (['https://app1.com', 'https://app2.com'])
  • RegExp: Pattern matching (/^https://.*\.myapp\.com$/)
  • Function: Dynamic validation ((origin) => isAllowed(origin))
  • Boolean: true for wildcard, false to disable CORS
  • Wildcard: '*' allows all origins (not recommended with credentials)
  • Wildcard origins (*) cannot be used with credentials for security
  • Origin validation is strict - malformed origins are rejected
  • Preflight requests are validated completely before processing
  • Invalid CORS requests receive proper 403 Forbidden responses
// Simple setup - allows all origins (development only)
const cors = new CORS();
router.use(cors);
const cors = new CORS({
origin: ['https://myapp.com', 'https://admin.myapp.com'],
credentials: true,
exposedHeaders: ['X-Total-Count']
});
router.use(cors);
const cors = new CORS({
origin: (origin) => {
// Allow subdomains of myapp.com
return origin && origin.match(/^https://.*\.myapp\.com$/);
},
credentials: true
});
const cors = new CORS({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
exposedHeaders: ['X-RateLimit-Remaining', 'X-RateLimit-Reset'],
credentials: true,
maxAge: 86400 // 24 hours
});

Hierarchy

  • Middleware
    • CORS

Constructors

  • Create CORS middleware with surgical security validation

    Integration Trap: Must register before route handlers to catch preflight OPTIONS requests. Security Constraint: Cannot combine wildcard origins with credentials per RFC 6454. Performance Note: Origin functions called for every request - keep validation logic fast.

    Parameters

    • Optionaloptions: {
          allowedHeaders: string[];
          credentials: boolean;
          exposedHeaders: string[];
          identifier: string;
          maxAge: number;
          methods: string[];
          origin:
              | string
              | boolean
              | RegExp
              | string[]
              | ((origin: string) => boolean);
      } = {}

      CORS configuration options

      • allowedHeaders: string[]

        Request headers whitelist

      • credentials: boolean

        Allow cookies/authorization headers (requires specific origins)

      • exposedHeaders: string[]

        Response headers visible to client JavaScript

      • identifier: string

        Middleware identifier for debugging

      • maxAge: number

        Preflight cache duration in seconds

      • methods: string[]

        Allowed HTTP methods

      • origin:
            | string
            | boolean
            | RegExp
            | string[]
            | ((origin: string) => boolean)

        Origin validation strategy

    Returns CORS

    Configuration validation failures preventing secure operation

    // Allow all origins - good for development
    const cors = new CORS({
    origin: true,
    credentials: false // Required with wildcard origins
    });
    // Strict origin control for production
    const cors = new CORS({
    origin: ['https://myapp.com', 'https://admin.myapp.com'],
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
    maxAge: 3600 // 1 hour cache
    });
    // Allow all subdomains of myapp.com
    const cors = new CORS({
    origin: /^https://.*\.myapp\.com$/,
    credentials: true
    });
    // Custom origin validation logic
    const cors = new CORS({
    origin: (origin) => {
    // Allow localhost in development
    if (process.env.NODE_ENV === 'development') {
    return origin?.includes('localhost');
    }
    // Production whitelist
    return ['https://myapp.com', 'https://admin.myapp.com'].includes(origin);
    }
    });

Properties

allowedHeaders: string[]
credentials: boolean
exposedHeaders: string[]
maxAge: number
methods: string[]
origin:
    | string
    | boolean
    | RegExp
    | string[]
    | ((origin: string) => boolean)

Accessors

  • get handler(): Handler
  • Gets the middleware handler function.

    Returns the function that will be executed when the middleware runs. This getter provides read-only access to the handler function.

    Returns Handler

    The handler function

    const middleware = new Middleware((ctx) => {
    console.log('Processing request');
    });

    const handler = middleware.handler;
    console.log(typeof handler); // 'function'

    // Can be used for testing or introspection
    if (handler.toString().includes('console.log')) {
    console.log('This middleware includes logging');
    }
  • get identifier(): string
  • Gets the middleware identifier.

    Returns the identifier string or null if no identifier was set. This getter provides read-only access to the identifier.

    Returns string

    The identifier or null if not set

    // Middleware with identifier
    const authMiddleware = new Middleware((ctx) => {}, 'auth');
    console.log(authMiddleware.identifier); // 'auth'

    // Middleware without identifier
    const loggingMiddleware = new Middleware((ctx) => {});
    console.log(loggingMiddleware.identifier); // null

    // Use identifier for conditional logic
    if (middleware.identifier === 'authentication') {
    console.log('This is an authentication middleware');
    }

Methods

  • Executes the middleware handler with the given context.

    This method invokes the middleware handler function, passing the context object as the first argument. The method supports both synchronous and asynchronous handlers, always returning a Promise.

    Execution Flow:

    1. Validates that ctx is a Context instance
    2. Calls the handler function with the context
    3. Returns a Promise that resolves when the handler completes
    4. Propagates any errors thrown by the handler

    Error Handling: Any errors thrown by the handler (synchronous or asynchronous) are propagated to the caller. This allows middleware errors to be handled by the calling code.

    Parameters

    • ctx: Context

      The request/response context

    Returns Promise<void>

    Promise that resolves when the handler completes

    When ctx is not a Context instance

    Any error thrown by the middleware handler

    const middleware = new Middleware((ctx) => {
    console.log(`Processing ${ctx.method} request to ${ctx.path}`);
    ctx.data.processedAt = new Date().toISOString();
    });

    const url = new URL('http://localhost/api/users');
    const ctx = new Context('GET', url, new Headers());

    // Execute middleware
    await middleware.execute(ctx);
    console.log(ctx.data.processedAt); // ISO timestamp

    // Async middleware
    const asyncMiddleware = new Middleware(async (ctx) => {
    await new Promise(resolve => setTimeout(resolve, 100));
    ctx.data.asyncProcessed = true;
    });

    await asyncMiddleware.execute(ctx);
    console.log(ctx.data.asyncProcessed); // true

    // Error handling
    const errorMiddleware = new Middleware((ctx) => {
    throw new Error('Middleware error');
    });

    try {
    await errorMiddleware.execute(ctx);
    } catch (error) {
    console.error('Middleware failed:', error.message);
    }

    // Invalid context
    try {
    await middleware.execute(null);
    } catch (error) {
    console.error(error.message); // "Context must be a Context instance"
    }
  • Checks if this middleware has the same identifier as another middleware.

    This method is used for duplicate detection and middleware comparison. Two middleware are considered to have the same identifier if:

    • Both have non-null identifiers
    • Both identifiers are exactly equal (string comparison)

    Note: Middleware with null identifiers are never considered equal, even if both have null identifiers.

    Parameters

    • other: Middleware

      The other middleware to compare with

    Returns boolean

    True if both middlewares have the same non-null identifier

    const auth1 = new Middleware((ctx) => {}, 'authentication');
    const auth2 = new Middleware((ctx) => {}, 'authentication');
    const logging = new Middleware((ctx) => {}, 'logging');
    const generic = new Middleware((ctx) => {});

    // Same identifier
    auth1.hasSameIdentifier(auth2); // true
    auth2.hasSameIdentifier(auth1); // true

    // Different identifiers
    auth1.hasSameIdentifier(logging); // false
    logging.hasSameIdentifier(auth1); // false

    // Null identifiers (never equal)
    auth1.hasSameIdentifier(generic); // false
    generic.hasSameIdentifier(auth1); // false
    generic.hasSameIdentifier(new Middleware((ctx) => {})); // false

    // Invalid parameters
    auth1.hasSameIdentifier(null); // false
    auth1.hasSameIdentifier({}); // false
    auth1.hasSameIdentifier('string'); // false

    // Use for duplicate prevention
    function addMiddlewareIfNotExists(middlewareList, newMiddleware) {
    const hasDuplicate = middlewareList.some(existing =>
    existing.hasSameIdentifier(newMiddleware)
    );

    if (!hasDuplicate) {
    middlewareList.push(newMiddleware);
    }
    }