The JWT authentication filter verifies a signed token on every request before it is routed: signature against a JWKS, plus issuer and audience checks. Use it when clients authenticate with bearer tokens from an identity provider and the gateway — not each backend — should reject invalid callers.
Enable it
Add envoy.filters.http.jwt_authn to the HTTP filter chain (before the router). A configuration has two parts: providers describe how to validate a token, rules decide which provider a request must satisfy.
http_filters:
- name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
idp:
issuer: https://idp.example.com
audiences: [api.example.com]
from_headers:
- name: Authorization
value_prefix: "Bearer "
local_jwks:
filename: jwks.json
rules:
- match: { prefix: "/" }
requires: { provider_name: idp }
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
Rules are evaluated in order and the first whose match fits the request decides the provider. A request that no rule maps to a provider is rejected — with this filter in the chain, every route needs a rule.
Always configure at least one token source (
from_headers,from_paramsorfrom_cookies). Arion has no implicit default; a provider without one rejects every request with 401.
Remote JWKS
Fetch keys from the identity provider instead of shipping them in the config. The JWKS host needs its own cluster; keys are fetched in the background and refreshed every cache_duration (default 60s), with an optional retry_policy.
providers:
idp:
issuer: https://idp.example.com
audiences: [api.example.com]
from_headers:
- name: Authorization
value_prefix: "Bearer "
remote_jwks:
http_uri:
uri: https://idp.example.com/.well-known/jwks.json
cluster: idp
timeout: 5s
cache_duration: 300sToken sources
A provider looks for the token in its configured from_headers (each with a value_prefix to strip, such as "Bearer "), then from_params query parameters, then from_cookies. After successful validation the token is removed from the forwarded request — header deleted, query parameter stripped — unless the provider sets forward: true.
Forward claims
claim_to_headers copies validated claims onto request headers for the backend; standard claims (sub, iss, aud, exp, iat, nbf, jti) and custom claims both work. payload_in_metadata stores the decoded claims on the request itself, which is how later filters — notably Cedar policies — see who the caller is.
providers:
idp:
# ...
payload_in_metadata: jwt_payload
claim_to_headers:
- claim_name: sub
header_name: x-jwt-sub
Validation details: issuer is checked when set, audiences when non-empty, and clock_skew_seconds adds leeway to exp/nbf checks. Decoded tokens are cached per worker, keyed by signature, so repeated requests skip signature verification.
Verify
curl -i http://127.0.0.1:8080/ # 401, no token
curl -i http://127.0.0.1:8080/ -H "Authorization: Bearer $TOKEN" # 200
Any failure — missing token, bad signature, wrong issuer or audience, expired — produces 401 Unauthorized with a short reason in the body.
Limits
- A rule requires exactly one
provider_name.requires_any,requires_all,allow_missing,allow_missing_or_failedandprovider_and_audiencesare refused at startup. - Named requirements (
requirement_map) andfilter_state_rulesare not supported. bypass_cors_preflightis not supported — place the CORS filter before this one so preflights are answered without a token.- There is no per-route override for this filter; route selection happens through
rules. require_expiration,max_lifetimeand the JWT cache size knob are not supported.