Octopus
1.x
Docs/Octopus/Authentication
Open

Reading5 min
Updated31 Jul 2026
Sourcev1/security/authentication.mdx

Authentication

Authentication is performed by the auth-gateway middleware. It runs once per request, picks exactly one provider, and either attaches an authenticated principal to the request or rejects it. This page covers provider selection and the request flow; each provider type has its own page.

When the middleware runs01

The auth-gateway middleware is added to the chain only when at least one provider is declared under auth_providers, or when auth.global_enforce is true. With no providers and no global enforcement, no auth middleware exists and every request passes through unauthenticated.

The request flow02

1

Skip OPTIONS. OPTIONS preflight requests bypass authentication entirely for CORS compatibility.

Skip global paths. If the request path matches any auth.skip_paths pattern, auth is skipped. Patterns are exact strings, or a trailing * wildcard that matches a path prefix (for example /public/* matches any path starting with /public/).

Skip the route. If the matched route has skip_auth: true, auth is skipped for that route.

Select the provider. The provider name is the route's auth_provider if set, otherwise the global auth.default_provider. If neither resolves to a name and auth.global_enforce is true, the request fails with 500 configuration_error. If neither resolves and enforcement is off, the request passes through unauthenticated.

Authenticate. The selected provider inspects the request (headers, query, and the verified TLS client CN for mTLS) and returns one of three outcomes:

  • Authenticated — a principal with id, name, roles, scopes, and the provider name.

  • Unauthenticated — no credentials were presented.

  • Failed — credentials were presented but invalid.

Authorize. On success, the route's role/scope/Rhai requirements and any global rules are evaluated (see Authorization).

Inject identity and forward. The principal's id, roles, and scopes are written to upstream request headers (X-Auth-Principal, X-Auth-Roles, X-Auth-Scopes by default), the principal is stored in request extensions, and a rate-limit key of user:<principal-id> is set for any downstream rate limiter.

What each outcome returns03

Outcomeglobal_enforce: falseglobal_enforce: true
AuthenticatedProceed to authorizationProceed to authorization
Unauthenticated (no credentials)Pass through unauthenticated401 unauthorized, WWW-Authenticate challenge
Failed (bad credentials)401 unauthorized with the failure reason401 unauthorized with the failure reason
Provider not found / errored500 auth_error500 auth_error
Warning

A Failed result (invalid credentials) always returns 401, regardless of global_enforce. Enforcement only changes what happens when no credentials are presented: enforced routes reject with 401, unenforced routes pass through.

Error responses are JSON: { "error": ..., "message": ..., "provider": ... }. For 401 responses a WWW-Authenticate header is added based on the provider type — Bearer for JWT, OIDC, and forward auth; ApiKey for API key; and none for mTLS.

Global auth settings04

These keys live under the top-level auth block. The fields that drive provider selection and the flow above are summarized here; the full reference is in configuration / auth.

KeyTypeDefaultDescription
default_providerstringnoneProvider applied to routes that do not set auth_provider.
global_enforcebooleanfalseWhen true, all non-skipped routes require successful authentication.
skip_pathslist of strings[]Paths that bypass auth. Exact match or trailing-* prefix.
principal_headerstringX-Auth-PrincipalHeader injected with the authenticated principal id.
roles_headerstringX-Auth-RolesHeader injected with the principal's roles (comma-joined).
scopes_headerstringX-Auth-ScopesHeader injected with the principal's scopes (comma-joined).
token_cache_ttlduration60sHow long a successful authentication is cached (see below).
error_formatstringjsonConfigured error format. The middleware emits JSON error bodies.
authzobjectAuthorization settings (see Authorization).

Roles and scopes headers are only injected when the principal actually has roles or scopes.

Per-route fields05

Each route may carry these authentication-related fields:

FieldTypeDefaultDescription
auth_providerstringnoneProvider name overriding auth.default_provider for this route.
skip_authbooleanfalseSkip authentication and authorization for this route.
require_roleslist of strings[]Authorization: principal must have at least one.
require_scopeslist of strings[]Authorization: principal must have all of them.
authz_rulestringnoneAuthorization: a Rhai expression evaluated for this route.

See routes configuration for the full route schema.

Token caching06

The provider registry caches successful authentications to avoid re-validating the same credential on every request. The cache key is derived per provider:

  • For requests with an Authorization header, the key is a SHA-256 hash of the provider name plus the header value — so JWT and OIDC bearer tokens are cached by their token value.

  • For mTLS, the key is mtls:<provider>:<cn>.

  • Requests with neither an Authorization header nor a client CN (for example an API key in a custom header only) are not cached.

A cached entry is reused while its age is below auth.token_cache_ttl (default 60s); on expiry it is evicted on next access. Only Authenticated results are cached — failures and unauthenticated results are never cached. Separately, a background task runs every 120 seconds and sweeps expired entries from the cache so memory does not grow unbounded.

Note

Because caching keys on the credential value (not the decoded claims), a token whose exp passes within the TTL window can still be accepted from cache until the entry expires. Keep token_cache_ttl short relative to your token lifetimes if this matters.

Example07

auth_providers:
  primary-jwt:
    type: jwt
    secret: "${JWT_SECRET}"
    algorithm: HS256
    issuer: https://auth.example.com

auth:
  default_provider: primary-jwt
  global_enforce: true
  skip_paths: ["/health", "/public/*"]
  token_cache_ttl: 60s

routes:
  - path: /health
    upstream: backend
    skip_auth: true
  - path: /admin/*
    upstream: backend
    auth_provider: primary-jwt
    require_roles: ["admin"]

Next08