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
SecurityPolicy with both jwt and basicAuth:
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
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,
requireAnyOne gate rejects → 401
- Request with invalid credentials of either type → the relevant filter rejects → 401
Summary
When a
SecurityPolicyis configured with bothjwtandbasicAuth, a request authenticated with a valid JWT Bearer token is rejected by the BasicAuth filter with:Similarly, a request authenticated with valid Basic credentials (and
jwt.optionalnot set) gets rejected by the JWT filter with: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
Steps to Reproduce
SecurityPolicywith bothjwtandbasicAuth: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: truefixes the Basic→JWT direction (JWT filter no longer rejects requests without a Bearer token), but the BasicAuth filter has no equivalentoptionalflag and always rejects requests whoseAuthorizationheader is notBasic.Related
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: trueto bothjwtandbasicAuthis 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
SecurityPolicylevel. For example, a top-level field:With this model:
requireAnyOnegate rejects → 401