Authentication gateway
The auth gateway (AuthGatewayMiddleware) is one of only two middleware Octopus
adds to its global chain from configuration. When active, it runs on every
request before proxying, authenticating the caller and enforcing authorization.
This page focuses on the middleware behavior. Provider configuration
(auth_providers) and authorization rules (auth.authz) are documented in full
under Authentication & authorization; the overall
trust model is in Security.
When it is active01
The middleware is added to the chain only when at least one of these holds:
auth_providershas one or more entries, orauth.global_enforceistrue.
If neither holds, no auth middleware is in the chain and all requests pass through unauthenticated.
When active, the server also instantiates each configured provider (JWT, OIDC,
API key, forward-auth, mTLS), builds the authorization evaluator from
auth.authz, and spawns a background task that prunes the token cache every
120 seconds.
Request flow02
For each request the middleware runs the following steps:
Skip OPTIONS
OPTIONS (CORS preflight) requests bypass auth entirely and pass through.
Skip configured paths
If the request path matches any pattern in
auth.skip_paths, auth is skipped. Patterns ending
in * are treated as prefix matches; otherwise an exact match is required.
Honor per-route skip_auth
If the matched route has skip_auth: true, auth is skipped. The route context
is injected into request extensions by the handler before the chain runs.
Select a provider
The provider is the route's auth_provider if set, otherwise
auth.default_provider. If neither resolves to a provider:
with
global_enforce: true, the request fails with500 configuration_error;otherwise it passes through.
Authenticate
The selected provider validates the request (bearer token, API key, client cert,
or external subrequest). Validated tokens are cached for auth.token_cache_ttl.
Authorize
On success, role/scope/authz_rule checks run against the matched route via the
authorization evaluator (Rhai and/or OPA per auth.authz.engine). A deny
produces 403 forbidden.
Inject principal headers and proxy
The authenticated principal's id, roles, and scopes are written to upstream request headers (see below), the principal is stored in request extensions, and the request continues to the proxy.
Injected upstream headers03
On successful authentication the middleware adds these headers to the request
forwarded upstream. The header names are configurable via auth.*:
| Header (config key) | Default | Contents |
auth.principal_header | X-Auth-Principal | The principal id |
auth.roles_header | X-Auth-Roles | Comma-separated roles (omitted if empty) |
auth.scopes_header | X-Auth-Scopes | Comma-separated scopes (omitted if empty) |
The middleware also stores an identity-based rate-limit key
(user:<principal-id>) in request extensions for a downstream rate limiter.
The auth gateway populates an AuthRateLimitKey extension, but the rate-limit
middleware that would read it is not in the runtime chain. So identity-based
rate limiting is plumbed but not active. See Rate limiting.
Error responses04
Errors are returned as JSON with an error and message field (provider name
included when known):
| Status | error | Cause |
401 | unauthorized | Failed credentials, or missing credentials while global_enforce: true |
403 | forbidden | Authenticated but authorization denied (roles/scopes/rule) |
500 | auth_error | The provider errored while validating |
500 | configuration_error | global_enforce on, but no provider could be selected |
For 401 responses, a WWW-Authenticate challenge is added based on the
provider type: Bearer for JWT/OIDC/forward-auth, ApiKey for API-key
providers, and none for mTLS.
With global_enforce: false, a request that presents no credentials passes
through unauthenticated (the upstream sees no principal headers). Only invalid
credentials produce 401. Set global_enforce: true to require authentication
on every non-skipped route.
Configuration05
The middleware is driven by the auth block and the auth_providers map. A
minimal example that activates the gateway with global enforcement and a single
JWT provider:
auth_providers:
primary:
type: jwt
secret: ${JWT_SECRET}
algorithm: HS256
issuer: https://issuer.example.com
audience: octopus
auth:
default_provider: primary
global_enforce: true
skip_paths:
- /health
- /public/*
token_cache_ttl: 60sSee Authentication & authorization for the full
schema of every provider type, the authz engine options, and per-route
overrides on Routes.
Related06
Authentication & authorization — provider and authz schema.
Routes — per-route
auth_provider,skip_auth, roles/scopes.Security — the overall security model.
Middleware overview — how this fits the global chain.