Skip to content

JWT authentication

Validate JSON Web Tokens against local or remote JWKS, choose which routes require which provider, and forward claims to backends.

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_params or from_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: 300s

Token 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_failed and provider_and_audiences are refused at startup.
  • Named requirements (requirement_map) and filter_state_rules are not supported.
  • bypass_cors_preflight is 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_lifetime and the JWT cache size knob are not supported.

Continue

Start typing to search the site.

↑ ↓ to move Enter to open Esc to close