csrf function

Middleware csrf({
  1. String? origin,
  2. List<String>? origins,
  3. Object? secFetchSite,
  4. bool originFn(
    1. String origin
    )?,
})

CSRF protection middleware.

Blocks state-mutating requests (POST, PUT, PATCH, DELETE, …) whose origin does not match the configured allow-list. Safe methods (GET, HEAD, OPTIONS) are always passed through.

Origin-based (default)

When no options are provided, the Origin header is compared against the request Host. Requests without an Origin header (e.g. same-origin server-to-server calls) are also passed.

app.use(csrf());

Static origin

app.use(csrf(origin: 'https://myapp.example.com'));

Multiple origins

app.use(csrf(origins: [
  'https://myapp.example.com',
  'https://dev.myapp.example.com',
]));

Sec-Fetch-Site header

Modern browsers send this header on every request. Use it as a lightweight alternative to origin matching (supports a single value or a list).

app.use(csrf(secFetchSite: 'same-origin'));
app.use(csrf(secFetchSite: ['same-origin', 'none']));

Custom function

app.use(csrf(
  originFn: (origin) =>
      RegExp(r'https://(\w+\.)?myapp\.example\.com$').hasMatch(origin),
));

Implementation

Middleware csrf({
  String? origin,
  List<String>? origins,
  Object? secFetchSite, // String | List<String>
  bool Function(String origin)? originFn,
}) {
  return (Context c, Next next) async {
    // Safe HTTP methods are always allowed
    if (_kSafeMethods.contains(c.req.method)) {
      await next();
      return;
    }

    // ── Sec-Fetch-Site check (takes priority when provided) ────────────────
    if (secFetchSite != null) {
      final site = c.req.header('sec-fetch-site') ?? '';
      final allowed = secFetchSite is List
          ? secFetchSite.cast<String>()
          : [secFetchSite as String];

      if (!allowed.contains(site)) {
        c.forbidden({'error': 'Forbidden'});
        return;
      }

      await next();
      return;
    }

    // ── Origin-based check ─────────────────────────────────────────────────
    final requestOrigin = c.req.header('origin') ?? '';

    final bool allowed;

    if (originFn != null) {
      allowed = originFn(requestOrigin);
    } else if (origins != null) {
      allowed = origins.contains(requestOrigin);
    } else if (origin != null) {
      allowed = requestOrigin == origin;
    } else {
      // Default: allow when Origin is absent or matches the request Host.
      if (requestOrigin.isEmpty) {
        allowed = true;
      } else {
        final host = (c.req.header('host') ?? '').split(':').first;
        final uri = Uri.tryParse(requestOrigin);
        allowed = uri != null && uri.host == host;
      }
    }

    if (!allowed) {
      c.forbidden({'error': 'Forbidden'});
      return;
    }

    await next();
  };
}