cors function

Middleware cors({
  1. String origin = '*',
  2. List<String>? allowHeaders,
  3. List<String>? allowMethods,
  4. List<String>? exposeHeaders,
  5. int? maxAge,
  6. bool credentials = false,
  7. String originFn(
    1. String origin
    )?,
  8. List<String> allowMethodsFn(
    1. String origin,
    2. Context c
    )?,
})

CORS middleware.

Sets the appropriate Access-Control-* headers and handles preflight (OPTIONS) requests automatically.

// Permissive default (origin: *)
app.mount('/api/*', cors());

// Static configuration
app.mount('/api/*', cors(
  origin: 'https://example.com',
  allowHeaders: ['X-Custom-Header', 'Authorization'],
  allowMethods: ['GET', 'POST', 'DELETE'],
  exposeHeaders: ['Content-Length'],
  maxAge: 600,
  credentials: true,
));

// Dynamic origin
app.mount('/api/*', cors(
  originFn: (origin) =>
      origin.endsWith('.example.com') ? origin : '*',
));

// Dynamic methods per origin
app.mount('/api/*', cors(
  allowMethodsFn: (origin, c) =>
      origin == 'https://admin.example.com'
          ? ['GET', 'POST', 'PATCH', 'DELETE']
          : ['GET'],
));

Implementation

Middleware cors({
  String origin = '*',
  List<String>? allowHeaders,
  List<String>? allowMethods,
  List<String>? exposeHeaders,
  int? maxAge,
  bool credentials = false,
  String Function(String origin)? originFn,
  List<String> Function(String origin, Context c)? allowMethodsFn,
}) {
  return (Context c, Next next) async {
    final requestOrigin = c.req.header('origin') ?? '';

    // ── Resolve allowed origin ─────────────────────────────────────────────
    final allowedOrigin =
        originFn != null ? originFn(requestOrigin) : origin;

    // ── Common headers (set on every response) ─────────────────────────────
    c.header('Access-Control-Allow-Origin', allowedOrigin);

    if (credentials) {
      c.header('Access-Control-Allow-Credentials', 'true');
    }

    if (exposeHeaders != null && exposeHeaders.isNotEmpty) {
      c.header('Access-Control-Expose-Headers', exposeHeaders.join(', '));
    }

    // Vary so caches don't serve the wrong origin's response
    if (originFn != null || allowMethodsFn != null) {
      c.header('Vary', 'Origin');
    }

    // ── Preflight ──────────────────────────────────────────────────────────
    if (c.req.method == 'OPTIONS') {
      final methods = allowMethodsFn != null
          ? allowMethodsFn(requestOrigin, c)
          : (allowMethods ?? _kDefaultMethods);

      c.header('Access-Control-Allow-Methods', methods.join(', '));
      c.header(
        'Access-Control-Allow-Headers',
        (allowHeaders ?? _kDefaultHeaders).join(', '),
      );

      if (maxAge != null) {
        c.header('Access-Control-Max-Age', '$maxAge');
      }

      c.noContent();
      return;
    }

    await next();
  };
}