SecurityPolicy: JWT Bearer requests rejected by BasicAuth filter when both are configured (no OR semantics)
#8491 aperta il 11 mar 2026
Metriche repository
- Star
- (2871 star)
- Metriche merge PR
- (Metriche PR in attesa)
Descrizione
Summary
When a SecurityPolicy is configured with both jwt and basicAuth, a request authenticated with a valid JWT Bearer token is rejected by the BasicAuth filter with:
HTTP/2 401
www-authenticate: Basic realm="..."
User authentication failed. Expected 'Basic' authentication scheme.
Similarly, a request authenticated with valid Basic credentials (and jwt.optional not set) gets rejected by the JWT filter with:
HTTP/2 401
www-authenticate: Bearer realm="..."
Jwt is missing
Neither direction works — the two filters apply AND logic in practice, even though users expect OR semantics (accept a request if any one of the configured auth methods succeeds).
Environment
- Envoy Gateway version: v1.6.3
- Kubernetes Gateway API version: v1.2.x
Steps to Reproduce
- Create a
SecurityPolicywith bothjwtandbasicAuth:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: multi-auth
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: my-route
jwt:
providers:
- name: my-provider
issuer: "https://issuer.example.com"
remoteJWKS:
uri: "https://issuer.example.com/keys"
basicAuth:
users:
name: my-htpasswd-secret
- Send a request with a valid JWT Bearer token → 401 "Expected 'Basic' authentication scheme"
- Send a request with valid Basic credentials → 401 "Jwt is missing"
Expected Behavior
A request authenticated via either mechanism should be accepted. If JWT validation succeeds, the BasicAuth filter should not run (or should pass through). Likewise, if the request presents valid Basic credentials and no JWT, the JWT filter should not block it. Requests with no credentials should still be rejected.
Actual Behavior
Both filters always run sequentially. Whichever runs second rejects the request if it does not carry the expected credential type for that filter.
Setting jwt.optional: true fixes the Basic→JWT direction (JWT filter no longer rejects requests without a Bearer token), but the BasicAuth filter has no equivalent optional flag and always rejects requests whose Authorization header is not Basic.
Related
- Discussion #2425 describes the same root problem for OIDC+JWT
- Issue #5367 touches on filter ordering
Workaround
The only currently working workaround is architectural: use separate SecurityPolicies per route, routing JWT-authenticated clients to read routes and BasicAuth-authenticated clients to write routes. This is restrictive and may not suit all use cases.
Proposed Fix
The naive approach of adding optional: true to both jwt and basicAuth is not sufficient: if both filters are individually optional, a request with no credentials at all would pass through both filters unauthenticated.
What is needed is "require at least one" semantics at the SecurityPolicy level. For example, a top-level field:
requireAnyOne: true # reject if no auth method succeeded; accept if any one did
jwt:
optional: true # pass through if no Bearer token present
providers: [...]
basicAuth:
optional: true # pass through if no Basic credentials present
users:
name: my-secret
With this model:
- Request with valid JWT Bearer → JWT filter validates, BasicAuth passes through → accepted
- Request with valid Basic credentials → JWT filter passes through, BasicAuth validates → accepted
- Request with no credentials → both filters pass through,
requireAnyOnegate rejects → 401 - Request with invalid credentials of either type → the relevant filter rejects → 401