Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Middlewares

Middlewares process requests before they reach dispatchers and can modify responses on the way back. They handle cross-cutting concerns like authentication, rate limiting, transformation, and caching.

This guide splits middlewares by concern:


Declaring middlewares

Middlewares are declared with the x-barbacane-middlewares extension — either at the root of a spec (global) or on a single operation:

x-barbacane-middlewares:
  - name: <middleware-name>
    config:
      # middleware-specific config

The chain

Middlewares execute in list order on the request path and in reverse on the response path:

Request  →  [MW 1]  →  [MW 2]  →  [MW 3]  →  Dispatcher
                                                  │
Response ←  [MW 1]  ←  [MW 2]  ←  [MW 3]  ←──────┘

Each entry in the list is an independent plugin instance with its own config and its own runtime state. Barbacane places no uniqueness constraint on the list — a plugin may appear any number of times.

Stacking

Any middleware can appear multiple times in a chain. Each entry is executed independently; there is no name-based deduplication, no “second entry wins” — every entry runs, in the order you wrote it.

Patterns that rely on stacking:

  • cel with on_match.set_context — one entry per routing rule. Each writes context keys that downstream plugins read. See Policy-driven routing.
  • ai-token-limit with distinct policy_name — multiple windows (per-minute, per-hour). See Stacking multiple windows.
  • rate-limit with distinct partition_key — layered limits (per-IP, per-user, per-tenant). See Layered rate limits.

Stacking is the primary composition mechanism. If a plugin’s feature set feels constrained, stacking another instance is usually the answer before reaching for config complexity.

Global vs operation merge

Global middlewares apply to every operation. Operations can add their own middlewares; the two lists are merged:

x-barbacane-middlewares:
  - name: correlation-id
  - name: cors
    config:
      allowed_origins: ["https://app.example.com"]

paths:
  /admin/users:
    get:
      x-barbacane-middlewares:
        - name: jwt-auth
          config:
            issuer: "https://auth.example.com"
      x-barbacane-dispatch:
        name: http-upstream
        config:
          url: "https://api.internal"
# Resolved chain: correlation-id → cors → jwt-auth

Name-based override. When an operation entry has the same name as an entry in the global chain, all global entries with that name are dropped and the operation entries are appended in their declared order.

# Global: rate-limit at 100/min + cors
x-barbacane-middlewares:
  - name: rate-limit
    config: { quota: 100, window: 60 }
  - name: cors
    config: { allow_origin: "*" }

paths:
  /public/feed:
    get:
      x-barbacane-middlewares:
        - name: rate-limit
          config: { quota: 1000, window: 60 }
      # Resolved chain: cors (global) → rate-limit (operation — replaced global)

Consequence for stacked plugins. A stack of cel entries at global level is replaced entirely if the operation declares any cel entry. To keep a global stack and add to it, re-declare the full stack at the operation level. (In practice, stack at one level.)

Disabling all middlewares. Use an empty array to opt a single operation out of the global chain:

paths:
  /internal/health:
    get:
      x-barbacane-middlewares: []  # Empty chain, globals ignored

Consumer identity headers

All authentication middlewares set two standard headers on successful authentication, in addition to their plugin-specific headers:

HeaderDescriptionExample
x-auth-consumerCanonical consumer identifier"alice", "user-123"
x-auth-consumer-groupsComma-separated group/role memberships"admin,editor", "read"

These standard headers enable downstream middlewares (like acl) to enforce authorization without coupling to a specific auth plugin.

Pluginx-auth-consumer sourcex-auth-consumer-groups source
basic-authusernameroles array
jwt-authsub claimconfigurable via groups_claim
oidc-authsub claimscope claim (space→comma)
oauth2-authsub claim (fallback: username)scope claim (space→comma)
apikey-authid fieldscopes array

Context passing

Middlewares can write and read a per-request key-value context. The chain’s order defines visibility: a value set by middleware N is visible to every downstream middleware and to the dispatcher, and — after dispatch — to every middleware in the on_response chain.

x-barbacane-middlewares:
  - name: jwt-auth          # writes context:auth.sub
    config: { issuer: "https://auth.example.com" }
  - name: rate-limit        # reads context:auth.sub
    config:
      quota: 100
      window: 60
      partition_key: "context:auth.sub"

The dispatcher may also write context keys (e.g. ai-proxy writes ai.prompt_tokens after calling the LLM) that flow into the on_response chain — see AI Gateway for the full map.


Best practices

Order matters

Put middlewares in logical order:

x-barbacane-middlewares:
  - name: correlation-id       # 1. Add tracing ID first
  - name: http-log             # 2. Log all requests (captures full lifecycle)
  - name: cors                 # 3. Handle CORS early
  - name: ip-restriction       # 4. Block bad IPs immediately
  - name: request-size-limit   # 5. Reject oversized requests
  - name: rate-limit           # 6. Rate limit before auth (cheaper)
  - name: oidc-auth            # 7. Authenticate
  - name: acl                  # 8. Authorize (after auth sets consumer headers)
  - name: request-transformer  # 9. Transform request before dispatch
  - name: response-transformer # 10. Transform response (runs first on the return)

Fail fast

Put restrictive middlewares early to reject bad requests before spending work on them:

x-barbacane-middlewares:
  - name: ip-restriction      # Block banned IPs immediately
  - name: request-size-limit  # Reject large payloads early
  - name: rate-limit          # Reject over-limit immediately
  - name: jwt-auth            # Reject unauthenticated before processing

Use global for common concerns

Set shared middlewares once at the root and only add operation-level entries for exceptions:

x-barbacane-middlewares:
  - name: correlation-id
  - name: cors
  - name: request-size-limit
    config:
      max_bytes: 10485760  # 10 MiB default
  - name: rate-limit
    config: { quota: 100, window: 60 }

paths:
  /upload:
    post:
      # Override only the size limit for uploads. CORS, correlation-id,
      # rate-limit still apply from global.
      x-barbacane-middlewares:
        - name: request-size-limit
          config:
            max_bytes: 104857600  # 100 MiB

Remember: if the operation entry’s name matches a global entry, the entire matching global group is replaced. If the global has a stack of a given plugin and the operation overrides one of them, move the full stack to the operation level.


Planned middlewares

idempotency

Ensures idempotent processing via Idempotency-Key header. Not yet shipped.

x-barbacane-middlewares:
  - name: idempotency
    config:
      header: Idempotency-Key
      ttl: 86400

See ROADMAP.md for scheduling.