Skip to content

SecurityPolicy: JWT Bearer requests rejected by BasicAuth filter when both are configured (no OR semantics) #8491

@QuentinBisson

Description

@QuentinBisson

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

  1. 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
  1. Send a request with a valid JWT Bearer token → 401 "Expected 'Basic' authentication scheme"
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions