Skip to content

wheels.middleware.Cors cannot short-circuit OPTIONS preflight — middleware runs after route dispatch #2703

Description

@bpamiri

Severity: high — the new middleware is unusable for the canonical CORS use-case (cross-origin POST/PUT/PATCH/DELETE) without an upstream fix.

Surfaced by: titan Phase 2.6 (Wheels 4.0 upgrade), 2026-05-15. The legacy set(allowCorsRequests=true) global path handled preflight; the new middleware cannot.

Repro

Configure a Wheels 4.0 app with no explicit OPTIONS route on /api/v1/*:

set(middleware = [
    new wheels.middleware.Cors(allowOrigins="https://portal.pai.com")
]);

Then:

OPTIONS /api/v1/jvm HTTP/1.1
Origin: https://portal.pai.com
Access-Control-Request-Method: POST

HTTP/1.1 404 with Wheels.RouteNotFound — Incorrect HTTP Verb for route (from Dispatch.cfc:214).

Expected: 204 No Content with Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers. Browsers will block the actual cross-origin request when preflight fails.

Root cause

In vendor/wheels/Dispatch.cfc:$request():

  • Line 260: $paramParser() runs
  • Line 260+: $findMatchingRoute() 404s when no OPTIONS route matches
  • Line 320: middleware pipeline is built — never reached on the OPTIONS path

wheels.middleware.Cors has explicit preflight-handling code at Cors.cfc:89-95 that is unreachable.

For comparison, the legacy set(allowCorsRequests=true) path aborts OPTIONS at EventMethods.cfc:255-262, BEFORE dispatch route-matching.

Suggested fix

One of:

  1. Run a pre-route middleware pass (CORS middleware specifically — or a designated subset — runs before $findMatchingRoute()).
  2. Auto-register an OPTIONS verb on every route added via .resources() and friends, dispatching to a built-in preflight handler when CORS middleware is present.
  3. Special-case OPTIONS in the dispatcher: if the middleware pipeline contains a wheels.middleware.Cors instance, route OPTIONS to its preflight handler before 404'ing.

Why this matters for v4.0.1

This is the entire reason apps reach for a CORS middleware over the legacy global setting. Without preflight handling, the middleware is strictly less capable than the 3.x setting it's meant to replace.

cc @bpamiri

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions