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’re used for cross-cutting concerns like authentication, rate limiting, and caching.

Overview

Middlewares are configured with x-barbacane-middlewares:

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

Middleware Chain

Middlewares execute in order:

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

Global vs Operation Middlewares

Global Middlewares

Apply to all operations:

openapi: "3.1.0"
info:
  title: My API
  version: "1.0.0"

# These apply to every operation
x-barbacane-middlewares:
  - name: request-id
    config:
      header: X-Request-ID
  - name: cors
    config:
      allowed_origins: ["https://app.example.com"]

paths:
  /users:
    get:
      # Inherits global middlewares
      x-barbacane-dispatch:
        name: http-upstream
        config:
          url: "https://api.example.com"

Operation Middlewares

Apply to specific operations (run after global):

paths:
  /admin/users:
    get:
      x-barbacane-middlewares:
        - name: jwt-auth
          config:
            required: true
            scopes: ["admin:read"]
      x-barbacane-dispatch:
        name: http-upstream
        config:
          url: "https://api.example.com"

Merging with Global Middlewares

When an operation declares its own middlewares, they are merged with the global chain:

  • Global middlewares run first, in order
  • If an operation middleware has the same name as a global one, the operation config overrides that global entry
  • Non-overridden global middlewares are preserved
# 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:
      # Override rate-limit, cors is still applied from globals
      x-barbacane-middlewares:
        - name: rate-limit
          config:
            quota: 1000
            window: 60
      # Resolved chain: cors (global) → rate-limit (operation override)

To explicitly disable all middlewares for an operation, use an empty array:

paths:
  /internal/health:
    get:
      x-barbacane-middlewares: []  # No middlewares at all

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

Authentication Middlewares

jwt-auth

Validates JWT tokens with RS256/HS256 signatures.

x-barbacane-middlewares:
  - name: jwt-auth
    config:
      issuer: "https://auth.example.com"  # Optional: validate iss claim
      audience: "my-api"                  # Optional: validate aud claim
      groups_claim: "roles"               # Optional: claim name for consumer groups
      skip_signature_validation: true     # Required until JWKS support is implemented

Accepted algorithms: RS256, RS384, RS512, ES256, ES384, ES512. HS256/HS512 and none are rejected.

Note: Cryptographic signature validation is not yet implemented. Set skip_signature_validation: true in production until JWKS support lands. Without it, all tokens are rejected with 401 at the signature step.

Configuration

PropertyTypeDefaultDescription
issuerstring-Expected iss claim. Tokens not matching are rejected
audiencestring-Expected aud claim. Tokens not matching are rejected
clock_skew_secondsinteger60Tolerance in seconds for exp/nbf validation
groups_claimstring-Claim name to extract consumer groups from (e.g., "roles", "groups"). Value is set as x-auth-consumer-groups
skip_signature_validationbooleanfalseSkip cryptographic signature check. Required until JWKS support is implemented

Context Headers

Sets headers for downstream:

  • x-auth-consumer - Consumer identifier (from sub claim)
  • x-auth-consumer-groups - Comma-separated groups (from groups_claim, if configured)
  • x-auth-sub - Subject (user ID)
  • x-auth-claims - Full JWT claims as JSON

apikey-auth

Validates API keys from header or query parameter.

x-barbacane-middlewares:
  - name: apikey-auth
    config:
      key_location: header        # or "query"
      header_name: X-API-Key      # when key_location is "header"
      query_param: api_key        # when key_location is "query"
      keys:
        sk_live_abc123:
          id: key-001
          name: Production Key
          scopes: ["read", "write"]
        sk_test_xyz789:
          id: key-002
          name: Test Key
          scopes: ["read"]

Configuration

PropertyTypeDefaultDescription
key_locationstringheaderWhere to find key (header or query)
header_namestringX-API-KeyHeader name (when key_location: header)
query_paramstringapi_keyQuery param name (when key_location: query)
keysobject{}Map of valid API keys to metadata

Context Headers

Sets headers for downstream:

  • x-auth-consumer - Consumer identifier (from key id)
  • x-auth-consumer-groups - Comma-separated groups (from key scopes)
  • x-auth-key-id - Key identifier
  • x-auth-key-name - Key human-readable name
  • x-auth-key-scopes - Comma-separated scopes

oauth2-auth

Validates Bearer tokens via RFC 7662 token introspection.

x-barbacane-middlewares:
  - name: oauth2-auth
    config:
      introspection_endpoint: https://auth.example.com/oauth2/introspect
      client_id: my-api-client
      client_secret: "env://OAUTH2_CLIENT_SECRET"  # resolved at startup
      required_scopes: "read write"                 # space-separated
      timeout: 5.0                                  # seconds

The client_secret uses a secret reference (env://) which is resolved at gateway startup. See Secrets for details.

Configuration

PropertyTypeDefaultDescription
introspection_endpointstringrequiredRFC 7662 introspection URL
client_idstringrequiredClient ID for introspection auth
client_secretstringrequiredClient secret for introspection auth
required_scopesstring-Space-separated required scopes
timeoutfloat5.0Introspection request timeout (seconds)

Context Headers

Sets headers for downstream:

  • x-auth-consumer - Consumer identifier (from sub, fallback to username)
  • x-auth-consumer-groups - Comma-separated groups (from scope)
  • x-auth-sub - Subject
  • x-auth-scope - Token scopes
  • x-auth-client-id - Client ID
  • x-auth-username - Username (if present)
  • x-auth-claims - Full introspection response as JSON

Error Responses

  • 401 Unauthorized - Missing token, invalid token, or inactive token
  • 403 Forbidden - Token lacks required scopes

Includes RFC 6750 WWW-Authenticate header with error details.


oidc-auth

OpenID Connect authentication via OIDC Discovery and JWKS. Automatically fetches the provider’s signing keys and validates JWT tokens with full cryptographic verification.

x-barbacane-middlewares:
  - name: oidc-auth
    config:
      issuer_url: https://accounts.google.com
      audience: my-api-client-id
      required_scopes: "openid profile email"
      issuer_override: https://external.example.com  # optional
      clock_skew_seconds: 60
      jwks_refresh_seconds: 300
      timeout: 5.0
      allow_query_token: false  # RFC 6750 §2.3 query param fallback

Configuration

PropertyTypeDefaultDescription
issuer_urlstringrequiredOIDC issuer URL (e.g., https://accounts.google.com)
audiencestring-Expected aud claim. If set, tokens must match
required_scopesstring-Space-separated required scopes
issuer_overridestring-Override expected iss claim (for split-network setups like Docker)
clock_skew_secondsinteger60Clock skew tolerance for exp/nbf validation
jwks_refresh_secondsinteger300How often to refresh JWKS keys (seconds)
timeoutfloat5.0HTTP timeout for discovery and JWKS calls (seconds)
allow_query_tokenbooleanfalseAllow token extraction from the access_token query parameter (RFC 6750 §2.3). Use with caution — tokens in URLs risk leaking via logs and referer headers.

How It Works

  1. Extracts the Bearer token from the Authorization header (or from the access_token query parameter if allow_query_token is enabled and no header is present)
  2. Parses the JWT header to determine the signing algorithm and key ID (kid)
  3. Fetches {issuer_url}/.well-known/openid-configuration (cached)
  4. Fetches the JWKS endpoint from the discovery document (cached with TTL)
  5. Finds the matching public key by kid (or kty/use fallback)
  6. Verifies the signature using host_verify_signature (RS256/RS384/RS512, ES256/ES384)
  7. Validates claims: iss, aud, exp, nbf
  8. Checks required scopes (if configured)

Context Headers

Sets headers for downstream:

  • x-auth-consumer - Consumer identifier (from sub claim)
  • x-auth-consumer-groups - Comma-separated groups (from scope, space→comma)
  • x-auth-sub - Subject (user ID)
  • x-auth-scope - Token scopes
  • x-auth-claims - Full JWT payload as JSON

Error Responses

  • 401 Unauthorized - Missing token, invalid token, expired token, bad signature, unknown issuer
  • 403 Forbidden - Token lacks required scopes

Includes RFC 6750 WWW-Authenticate header with error details.


basic-auth

Validates credentials from the Authorization: Basic header per RFC 7617. Useful for internal APIs, admin endpoints, or simple services that don’t need a full identity provider.

x-barbacane-middlewares:
  - name: basic-auth
    config:
      realm: "My API"
      strip_credentials: true
      credentials:
        admin:
          password: "env://ADMIN_PASSWORD"
          roles: ["admin", "editor"]
        readonly:
          password: "env://READONLY_PASSWORD"
          roles: ["viewer"]

Configuration

PropertyTypeDefaultDescription
realmstringapiAuthentication realm shown in WWW-Authenticate challenge
strip_credentialsbooleantrueRemove Authorization header before forwarding to upstream
credentialsobject{}Map of username to credential entry

Each credential entry:

PropertyTypeDefaultDescription
passwordstringrequiredPassword for this user (supports secret references)
rolesarray[]Optional roles for authorization

Context Headers

Sets headers for downstream:

  • x-auth-consumer - Consumer identifier (username)
  • x-auth-consumer-groups - Comma-separated groups (from roles)
  • x-auth-user - Authenticated username
  • x-auth-roles - Comma-separated roles (only set if the user has roles)

Error Responses

Returns 401 Unauthorized with WWW-Authenticate: Basic realm="<realm>" and Problem JSON:

{
  "type": "urn:barbacane:error:authentication-failed",
  "title": "Authentication failed",
  "status": 401,
  "detail": "Invalid username or password"
}

Authorization Middlewares

acl

Enforces access control based on consumer identity and group membership. Reads the standard x-auth-consumer and x-auth-consumer-groups headers set by upstream auth plugins.

x-barbacane-middlewares:
  - name: basic-auth
    config:
      realm: "my-api"
      credentials:
        admin:
          password: "env://ADMIN_PASSWORD"
          roles: ["admin", "editor"]
        viewer:
          password: "env://VIEWER_PASSWORD"
          roles: ["viewer"]
  - name: acl
    config:
      allow:
        - admin
      deny:
        - banned

Configuration

PropertyTypeDefaultDescription
allowarray[]Group names allowed access. If non-empty, consumer must belong to at least one
denyarray[]Group names denied access (takes precedence over allow)
allow_consumersarray[]Specific consumer IDs allowed (bypasses group checks)
deny_consumersarray[]Specific consumer IDs denied (highest precedence)
consumer_groupsobject{}Static consumer-to-groups mapping, merged with x-auth-consumer-groups header
messagestringAccess denied by ACL policyCustom 403 error message
hide_consumer_in_errorsbooleanfalseSuppress consumer identity in 403 error body

Evaluation Order

  1. Missing/empty x-auth-consumer header → 403
  2. deny_consumers match → 403
  3. allow_consumers match → 200 (bypasses group checks)
  4. Resolve groups (merge x-auth-consumer-groups header + static consumer_groups config)
  5. deny group match → 403 (takes precedence over allow)
  6. allow non-empty + group match → 200
  7. allow non-empty + no group match → 403
  8. allow empty → 200 (only deny rules active)

Static Consumer Groups

You can supplement the groups from the auth plugin with static mappings:

- name: acl
  config:
    allow:
      - premium
    consumer_groups:
      free_user:
        - premium    # Grant premium access to specific consumers

Groups from the consumer_groups config are merged with the x-auth-consumer-groups header (deduplicated).

Error Response

Returns 403 Forbidden with Problem JSON (RFC 9457):

{
  "type": "urn:barbacane:error:acl-denied",
  "title": "Forbidden",
  "status": 403,
  "detail": "Access denied by ACL policy",
  "consumer": "alice"
}

Set hide_consumer_in_errors: true to omit the consumer field.

opa-authz

Policy-based access control via Open Policy Agent. Sends request context to an OPA REST API endpoint and enforces the boolean decision. Typically placed after an authentication middleware so that auth claims are available as OPA input.

x-barbacane-middlewares:
  - name: jwt-auth
    config:
      issuer: "https://auth.example.com"
      skip_signature_validation: true
  - name: opa-authz
    config:
      opa_url: "http://opa:8181/v1/data/authz/allow"

Configuration

PropertyTypeDefaultDescription
opa_urlstring(required)OPA Data API endpoint URL (e.g., http://opa:8181/v1/data/authz/allow)
timeoutnumber5HTTP request timeout in seconds for OPA calls
include_bodybooleanfalseInclude the request body in the OPA input payload
include_claimsbooleantrueInclude parsed x-auth-claims header (set by upstream auth plugins) in the OPA input
deny_messagestringAuthorization denied by policyCustom message returned in the 403 response body

OPA Input Payload

The plugin POSTs the following JSON to your OPA endpoint:

{
  "input": {
    "method": "GET",
    "path": "/admin/users",
    "query": "page=1",
    "headers": { "x-auth-consumer": "alice" },
    "client_ip": "10.0.0.1",
    "claims": { "sub": "alice", "roles": ["admin"] },
    "body": "..."
  }
}
  • claims is included only when include_claims is true and the x-auth-claims header contains valid JSON (set by auth plugins like jwt-auth, oauth2-auth)
  • body is included only when include_body is true

Decision Logic

The plugin expects OPA to return the standard Data API response:

{ "result": true }
OPA ResponseResult
{"result": true}200 — request continues
{"result": false}403 — access denied
{} (undefined document)403 — access denied
Non-boolean result403 — access denied
OPA unreachable or error503 — service unavailable

Error Responses

403 Forbidden — OPA denies access:

{
  "type": "urn:barbacane:error:opa-denied",
  "title": "Forbidden",
  "status": 403,
  "detail": "Authorization denied by policy"
}

503 Service Unavailable — OPA is unreachable or returns a non-200 status:

{
  "type": "urn:barbacane:error:opa-unavailable",
  "title": "Service Unavailable",
  "status": 503,
  "detail": "OPA service unreachable"
}

Example OPA Policy

package authz

default allow := false

# Allow admins everywhere
allow if {
    input.claims.roles[_] == "admin"
}

# Allow GET on public paths
allow if {
    input.method == "GET"
    startswith(input.path, "/public/")
}

cel

Inline policy evaluation using CEL (Common Expression Language). Evaluates expressions directly in-process — no external service needed. CEL is the same language used by Envoy, Kubernetes, and Firebase for policy rules.

x-barbacane-middlewares:
  - name: jwt-auth
    config:
      issuer: "https://auth.example.com"
  - name: cel
    config:
      expression: >
        'admin' in request.claims.roles
        || (request.method == 'GET' && request.path.startsWith('/public/'))

Configuration

PropertyTypeDefaultDescription
expressionstring(required)CEL expression that must evaluate to a boolean
deny_messagestringAccess denied by policyCustom message returned in the 403 response body

Request Context

The expression has access to a request object with these fields:

VariableTypeDescription
request.methodstringHTTP method (GET, POST, etc.)
request.pathstringRequest path (e.g., /api/users)
request.querystringQuery string (empty string if none)
request.headersmapRequest headers (e.g., request.headers.authorization)
request.bodystringRequest body (empty string if none)
request.client_ipstringClient IP address
request.path_paramsmapPath parameters (e.g., request.path_params.id)
request.consumerstringConsumer identity from x-auth-consumer header (empty if absent)
request.claimsmapParsed JSON from x-auth-claims header (empty map if absent/invalid)

CEL Features

CEL supports a rich expression language:

// String operations
request.path.startsWith('/api/')
request.path.endsWith('.json')
request.headers.host.contains('example')

// List operations
'admin' in request.claims.roles
request.claims.roles.exists(r, r == 'editor')

// Field presence
has(request.claims.email)

// Logical operators
request.method == 'GET' && request.consumer != ''
request.method in ['GET', 'HEAD', 'OPTIONS']
!(request.client_ip.startsWith('192.168.'))

Decision Logic

Expression ResultHTTP Response
trueRequest continues to next middleware/dispatcher
false403 Forbidden
Non-boolean500 Internal Server Error
Parse/evaluation error500 Internal Server Error

Error Responses

403 Forbidden — expression evaluates to false:

{
  "type": "urn:barbacane:error:cel-denied",
  "title": "Forbidden",
  "status": 403,
  "detail": "Access denied by policy"
}

500 Internal Server Error — invalid expression or non-boolean result:

{
  "type": "urn:barbacane:error:cel-evaluation",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "expression returned string, expected bool"
}

CEL vs OPA

celopa-authz
DeploymentEmbedded (no sidecar)External OPA server
LanguageCELRego
LatencyMicroseconds (in-process)HTTP round-trip
Best forInline route-level rulesComplex policy repos, audit trails

Rate Limiting

rate-limit

Limits request rate per client using a sliding window algorithm. Implements IETF draft-ietf-httpapi-ratelimit-headers.

x-barbacane-middlewares:
  - name: rate-limit
    config:
      quota: 100
      window: 60
      policy_name: default
      partition_key: client_ip

Configuration

PropertyTypeDefaultDescription
quotaintegerrequiredMaximum requests allowed in the window
windowintegerrequiredWindow duration in seconds
policy_namestringdefaultPolicy name for RateLimit-Policy header
partition_keystringclient_ipRate limit key source

Partition Key Sources

  • client_ip - Client IP from X-Forwarded-For or X-Real-IP
  • header:<name> - Header value (e.g., header:X-API-Key)
  • context:<key> - Context value (e.g., context:auth.sub)
  • Any static string - Same limit for all requests

Response Headers

On allowed requests:

  • X-RateLimit-Policy - Policy name and configuration
  • X-RateLimit-Limit - Maximum requests in window
  • X-RateLimit-Remaining - Remaining requests
  • X-RateLimit-Reset - Unix timestamp when window resets

On rate-limited requests (429):

  • RateLimit-Policy - IETF draft header
  • RateLimit - IETF draft combined header
  • Retry-After - Seconds until retry is allowed

CORS

cors

Handles Cross-Origin Resource Sharing per the Fetch specification. Processes preflight OPTIONS requests and adds CORS headers to responses.

x-barbacane-middlewares:
  - name: cors
    config:
      allowed_origins:
        - https://app.example.com
        - https://admin.example.com
      allowed_methods:
        - GET
        - POST
        - PUT
        - DELETE
      allowed_headers:
        - Authorization
        - Content-Type
      expose_headers:
        - X-Request-ID
      max_age: 86400
      allow_credentials: false

Configuration

PropertyTypeDefaultDescription
allowed_originsarray[]Allowed origins (["*"] for any, or specific origins)
allowed_methodsarray["GET", "POST"]Allowed HTTP methods
allowed_headersarray[]Allowed request headers (beyond simple headers)
expose_headersarray[]Headers exposed to browser JavaScript
max_ageinteger3600Preflight cache time (seconds)
allow_credentialsbooleanfalseAllow credentials (cookies, auth headers)

Origin Patterns

Origins can be:

  • Exact match: https://app.example.com
  • Wildcard subdomain: *.example.com (matches sub.example.com)
  • Wildcard: * (only when allow_credentials: false)

Error Responses

  • 403 Forbidden - Origin not in allowed list
  • 403 Forbidden - Method not allowed (preflight)
  • 403 Forbidden - Headers not allowed (preflight)

Preflight Responses

Returns 204 No Content with:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Max-Age
  • Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers

Request Tracing

correlation-id

Propagates or generates correlation IDs (UUID v7) for distributed tracing. The correlation ID is passed to upstream services and included in responses.

x-barbacane-middlewares:
  - name: correlation-id
    config:
      header_name: X-Correlation-ID
      generate_if_missing: true
      trust_incoming: true
      include_in_response: true

Configuration

PropertyTypeDefaultDescription
header_namestringX-Correlation-IDHeader name for the correlation ID
generate_if_missingbooleantrueGenerate new UUID v7 if not provided
trust_incomingbooleantrueTrust and propagate incoming correlation IDs
include_in_responsebooleantrueInclude correlation ID in response headers

Request Protection

ip-restriction

Allows or denies requests based on client IP address or CIDR ranges. Supports both allowlist and denylist modes.

x-barbacane-middlewares:
  - name: ip-restriction
    config:
      allow:
        - 10.0.0.0/8
        - 192.168.1.0/24
      deny:
        - 10.0.0.5
      message: "Access denied from your IP address"
      status: 403

Configuration

PropertyTypeDefaultDescription
allowarray[]Allowed IPs or CIDR ranges (allowlist mode)
denyarray[]Denied IPs or CIDR ranges (denylist mode)
messagestringAccess deniedCustom error message for denied requests
statusinteger403HTTP status code for denied requests

Behavior

  • If deny is configured, IPs in the list are blocked (denylist takes precedence)
  • If allow is configured, only IPs in the list are permitted (allowlist mode)
  • Client IP is extracted from X-Forwarded-For, X-Real-IP, or direct connection
  • Supports both single IPs (10.0.0.1) and CIDR notation (10.0.0.0/8)

Error Response

Returns Problem JSON (RFC 7807):

{
  "type": "urn:barbacane:error:ip-restricted",
  "title": "Forbidden",
  "status": 403,
  "detail": "Access denied",
  "client_ip": "203.0.113.50"
}

bot-detection

Blocks requests from known bots and scrapers by matching the User-Agent header against configurable deny patterns. An allow list lets trusted crawlers bypass the deny list.

x-barbacane-middlewares:
  - name: bot-detection
    config:
      deny:
        - scrapy
        - ahrefsbot
        - semrushbot
        - mj12bot
        - dotbot
      allow:
        - Googlebot
        - Bingbot
      block_empty_ua: false
      message: "Automated access is not permitted"
      status: 403

Configuration

PropertyTypeDefaultDescription
denyarray[]User-Agent substrings to block (case-insensitive substring match)
allowarray[]User-Agent substrings that override the deny list (trusted crawlers)
block_empty_uabooleanfalseBlock requests with no User-Agent header
messagestringAccess deniedCustom error message for blocked requests
statusinteger403HTTP status code for blocked requests

Behavior

  • Matching is case-insensitive substring: "bot" matches "AhrefsBot", "DotBot", etc.
  • The allow list takes precedence over deny: a UA matching both allow and deny is allowed through
  • Missing User-Agent is permitted by default; set block_empty_ua: true to block it
  • Both deny and allow are empty by default — the plugin is a no-op unless configured

Error Response

Returns Problem JSON (RFC 7807):

{
  "type": "urn:barbacane:error:bot-detected",
  "title": "Forbidden",
  "status": 403,
  "detail": "Access denied",
  "user_agent": "scrapy/2.11"
}

The user_agent field is omitted when the request had no User-Agent header.


request-size-limit

Rejects requests that exceed a configurable body size limit. Checks both Content-Length header and actual body size.

x-barbacane-middlewares:
  - name: request-size-limit
    config:
      max_bytes: 1048576        # 1 MiB
      check_content_length: true

Configuration

PropertyTypeDefaultDescription
max_bytesinteger1048576Maximum allowed request body size in bytes (default: 1 MiB)
check_content_lengthbooleantrueCheck Content-Length header for early rejection

Error Response

Returns 413 Payload Too Large with Problem JSON:

{
  "type": "urn:barbacane:error:payload-too-large",
  "title": "Payload Too Large",
  "status": 413,
  "detail": "Request body size 2097152 bytes exceeds maximum allowed size of 1048576 bytes."
}

Caching

cache

Caches responses in memory with TTL support.

x-barbacane-middlewares:
  - name: cache
    config:
      ttl: 300
      vary:
        - Accept-Language
        - Accept-Encoding
      methods:
        - GET
        - HEAD
      cacheable_status:
        - 200
        - 301

Configuration

PropertyTypeDefaultDescription
ttlinteger300Cache duration (seconds)
varyarray[]Headers that vary cache key
methodsarray["GET", "HEAD"]HTTP methods to cache
cacheable_statusarray[200, 301]Status codes to cache

Cache Key

Cache key is computed from:

  • HTTP method
  • Request path
  • Vary header values (if configured)

Cache-Control Respect

The middleware respects Cache-Control response headers:

  • no-store - Response not cached
  • no-cache - Cache but revalidate
  • max-age=N - Use specified TTL instead of config

Logging

http-log

Sends structured JSON log entries to an HTTP endpoint for centralized logging. Captures request metadata, response status, timing, and optional headers/body sizes. Compatible with Datadog, Splunk, ELK, or any HTTP log ingestion endpoint.

x-barbacane-middlewares:
  - name: http-log
    config:
      endpoint: https://logs.example.com/ingest
      method: POST
      timeout_ms: 2000
      include_headers: false
      include_body: true
      custom_fields:
        service: my-api
        environment: production

Configuration

PropertyTypeDefaultDescription
endpointstringrequiredURL to send log entries to
methodstringPOSTHTTP method (POST or PUT)
timeout_msinteger2000Timeout for the log HTTP call (100-10000 ms)
content_typestringapplication/jsonContent-Type header for the log request
include_headersbooleanfalseInclude request and response headers in log entries
include_bodybooleanfalseInclude request and response body sizes in log entries
custom_fieldsobject{}Static key-value fields included in every log entry

Log Entry Format

Each log entry is a JSON object:

{
  "timestamp_ms": 1706500000000,
  "duration_ms": 42,
  "correlation_id": "abc-123",
  "request": {
    "method": "POST",
    "path": "/users",
    "query": "page=1",
    "client_ip": "10.0.0.1",
    "headers": { "content-type": "application/json" },
    "body_size": 256
  },
  "response": {
    "status": 201,
    "headers": { "content-type": "application/json" },
    "body_size": 64
  },
  "service": "my-api",
  "environment": "production"
}

Optional fields (correlation_id, headers, body_size, query) are omitted when not available or not enabled.

Behavior

  • Runs in the response phase (after dispatch) to capture both request and response data
  • Log delivery is best-effort — failures never affect the upstream response
  • The correlation_id field is automatically populated if the correlation-id middleware runs earlier in the chain
  • Custom fields are flattened into the top-level JSON object

Request Transformation

request-transformer

Declaratively modifies requests before they reach the dispatcher. Supports header, query parameter, path, and JSON body transformations with variable interpolation.

x-barbacane-middlewares:
  - name: request-transformer
    config:
      headers:
        add:
          X-Gateway: "barbacane"
          X-Client-IP: "$client_ip"
        set:
          X-Request-Source: "external"
        remove:
          - Authorization
          - X-Internal-Token
        rename:
          X-Old-Name: X-New-Name
      querystring:
        add:
          gateway: "barbacane"
          userId: "$path.userId"
        remove:
          - internal_token
        rename:
          oldParam: newParam
      path:
        strip_prefix: "/api/v1"
        add_prefix: "/internal"
        replace:
          pattern: "/users/(\\w+)/orders"
          replacement: "/v2/orders/$1"
      body:
        add:
          /metadata/gateway: "barbacane"
          /userId: "$path.userId"
        remove:
          - /password
          - /internal_flags
        rename:
          /userName: /user_name

Configuration

headers
PropertyTypeDefaultDescription
addobject{}Add or overwrite headers. Supports variable interpolation
setobject{}Add headers only if not already present. Supports variable interpolation
removearray[]Remove headers by name (case-insensitive)
renameobject{}Rename headers (old-name to new-name)
querystring
PropertyTypeDefaultDescription
addobject{}Add or overwrite query parameters. Supports variable interpolation
removearray[]Remove query parameters by name
renameobject{}Rename query parameters (old-name to new-name)
path
PropertyTypeDefaultDescription
strip_prefixstring-Remove prefix from path (e.g., /api/v2)
add_prefixstring-Add prefix to path (e.g., /internal)
replace.patternstring-Regex pattern to match in path
replace.replacementstring-Replacement string (supports regex capture groups)

Path operations are applied in order: strip prefix, add prefix, regex replace.

body

JSON body transformations use JSON Pointer (RFC 6901) paths.

PropertyTypeDefaultDescription
addobject{}Add or overwrite JSON fields. Supports variable interpolation
removearray[]Remove JSON fields by JSON Pointer path
renameobject{}Rename JSON fields (old-pointer to new-pointer)

Body transformations only apply to requests with application/json content type. Non-JSON bodies pass through unchanged.

Variable Interpolation

Values in add, set, and body add support variable templates:

VariableDescriptionExample
$client_ipClient IP address192.168.1.1
$header.<name>Request header value (case-insensitive)$header.host
$query.<name>Query parameter value$query.page
$path.<name>Path parameter value$path.userId
context:<key>Request context value (set by other middlewares)context:auth.sub

Variables always resolve against the original incoming request, regardless of transformations applied by earlier sections. This means a query parameter removed in querystring.remove is still available via $query.<name> in body.add.

If a variable cannot be resolved, it is replaced with an empty string.

Transformation Order

Transformations are applied in this order:

  1. Path — strip prefix, add prefix, regex replace
  2. Headers — add, set, remove, rename
  3. Query parameters — add, remove, rename
  4. Body — add, remove, rename

Use Cases

Strip API version prefix:

- name: request-transformer
  config:
    path:
      strip_prefix: "/api/v2"

Move query parameter to body (ADR-0020 showcase):

- name: request-transformer
  config:
    querystring:
      remove:
        - userId
    body:
      add:
        /userId: "$query.userId"

Add gateway metadata to every request:

# Global middleware
x-barbacane-middlewares:
  - name: request-transformer
    config:
      headers:
        add:
          X-Gateway: "barbacane"
          X-Client-IP: "$client_ip"

Response Transformation

response-transformer

Declaratively modifies responses before they return to the client. Supports status code mapping, header transformations, and JSON body transformations.

x-barbacane-middlewares:
  - name: response-transformer
    config:
      status:
        200: 201
        400: 403
        500: 503
      headers:
        add:
          X-Gateway: "barbacane"
          X-Frame-Options: "DENY"
        set:
          X-Content-Type-Options: "nosniff"
        remove:
          - Server
          - X-Powered-By
        rename:
          X-Old-Name: X-New-Name
      body:
        add:
          /metadata/gateway: "barbacane"
        remove:
          - /internal_flags
          - /debug_info
        rename:
          /userName: /user_name

Configuration

status

A mapping of upstream status codes to replacement status codes. Unmapped codes pass through unchanged.

status:
  200: 201    # Created instead of OK
  400: 422    # Unprocessable Entity instead of Bad Request
  500: 503    # Service Unavailable instead of Internal Server Error
headers
PropertyTypeDefaultDescription
addobject{}Add or overwrite response headers
setobject{}Add headers only if not already present in the response
removearray[]Remove headers by name (case-insensitive)
renameobject{}Rename headers (old-name to new-name)
body

JSON body transformations use JSON Pointer (RFC 6901) paths.

PropertyTypeDefaultDescription
addobject{}Add or overwrite JSON fields
removearray[]Remove JSON fields by JSON Pointer path
renameobject{}Rename JSON fields (old-pointer to new-pointer)

Body transformations only apply to responses with JSON bodies. Non-JSON bodies pass through unchanged.

Transformation Order

Transformations are applied in this order:

  1. Status — map status code
  2. Headers — remove, rename, set, add
  3. Body — remove, rename, add

Use Cases

Strip upstream server headers:

- name: response-transformer
  config:
    headers:
      remove: [Server, X-Powered-By, X-AspNet-Version]

Add security headers to all responses:

- name: response-transformer
  config:
    headers:
      add:
        X-Frame-Options: "DENY"
        X-Content-Type-Options: "nosniff"
        Strict-Transport-Security: "max-age=31536000"

Clean up internal fields from response body:

- name: response-transformer
  config:
    body:
      remove:
        - /internal_metadata
        - /debug_trace
        - /password_hash

Map status codes for API versioning:

- name: response-transformer
  config:
    status:
      200: 201

URL Redirection

redirect

Redirects requests based on configurable path rules. Supports exact path matching, prefix matching with path rewriting, configurable status codes (301/302/307/308), and query string preservation.

x-barbacane-middlewares:
  - name: redirect
    config:
      status_code: 302
      preserve_query: true
      rules:
        - path: /old-page
          target: /new-page
          status_code: 301
        - prefix: /api/v1
          target: /api/v2
        - target: https://fallback.example.com

Configuration

PropertyTypeDefaultDescription
status_codeinteger302Default HTTP status code for redirects (301, 302, 307, 308)
preserve_querybooleantrueAppend the original query string to the redirect target
rulesarrayrequiredRedirect rules evaluated in order; first match wins

Rule Properties

PropertyTypeDescription
pathstringExact path to match. Mutually exclusive with prefix
prefixstringPath prefix to match. The matched prefix is stripped and the remainder is appended to target
targetstringRequired. Redirect target URL or path
status_codeintegerOverride the top-level status_code for this rule

If neither path nor prefix is set, the rule matches all requests (catch-all).

Matching Behavior

  • Rules are evaluated in order. The first matching rule wins.
  • Exact match (path): redirects only when the request path equals the value exactly.
  • Prefix match (prefix): strips the matched prefix and appends the remainder to target. For example, prefix: /api/v1 with target: /api/v2 redirects /api/v1/users?page=2 to /api/v2/users?page=2.
  • Catch-all: omit both path and prefix to redirect all requests hitting the route.

Status Codes

CodeMeaningMethod preserved?
301Moved PermanentlyNo (may change to GET)
302FoundNo (may change to GET)
307Temporary RedirectYes
308Permanent RedirectYes

Use 307/308 when you need POST/PUT/DELETE requests to be retried with the same method.

Use Cases

Domain migration:

- name: redirect
  config:
    status_code: 301
    rules:
      - target: https://new-domain.com

API versioning:

- name: redirect
  config:
    rules:
      - prefix: /api/v1
        target: /api/v2
        status_code: 301

Multiple redirects:

- name: redirect
  config:
    rules:
      - path: /blog
        target: https://blog.example.com
        status_code: 301
      - path: /docs
        target: https://docs.example.com
        status_code: 301
      - prefix: /old-api
        target: /api

Planned Middlewares

The following middlewares are planned for future milestones:

idempotency

Ensures idempotent processing.

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

Configuration

PropertyTypeDefaultDescription
headerstringIdempotency-KeyHeader containing key
ttlinteger86400Key expiration (seconds)

Context Passing

Middlewares can set context for downstream components:

# Auth middleware sets context:auth.sub
x-barbacane-middlewares:
  - name: auth-jwt
    config:
      required: true

# Rate limit uses auth context
  - name: rate-limit
    config:
      partition_key: context:auth.sub  # Rate limit per user

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 (OIDC/JWT)
  - name: basic-auth           # 8. Authenticate (fallback)
  - name: acl                  # 9. Authorize (after auth sets consumer headers)
  - name: request-transformer   # 10. Transform request before dispatch
  - name: response-transformer  # 11. Transform response before client (runs first in reverse)

Fail Fast

Put restrictive middlewares early to reject bad requests quickly:

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 unauthorized before processing

Use Global for Common Concerns

# Global: apply to everything
x-barbacane-middlewares:
  - name: correlation-id
  - name: cors
  - name: request-size-limit
    config:
      max_bytes: 10485760  # 10 MiB global limit
  - name: rate-limit

paths:
  /public:
    get:
      # No additional middlewares needed

  /private:
    get:
      # Only add what's different
      x-barbacane-middlewares:
        - name: auth-jwt

  /upload:
    post:
      # Override size limit for uploads
      x-barbacane-middlewares:
        - name: request-size-limit
          config:
            max_bytes: 104857600  # 100 MiB for uploads