Skip to content

feat(jwt): validate the aud (audience) claim on authentication.jwt #664

Description

@Aman-Cool

Is your feature request related to some problem you are facing? Please describe that problem here.

JWT authentication (authentication.jwt) verifies the token signature and expiry, and; once #657 lands: optionally the iss claim via authentication.jwt.issuer. It still does not verify the aud (audience) claim.

Where several services sit behind the same issuer (the usual Keycloak/Auth0/Entra setup), a token legitimately minted for service A authenticates to service B on signature alone; a cross-service token-replay gap. Authorino already validates audiences for kubernetesTokenReview (audiences) but not for JWTs, its main authentication method. RFC 7519 §4.1.3 and RFC 8725 (JWT BCP) §3.9 both call for the recipient to reject a token not intended for it.

Describe the solution you'd like

Add an optional authentication.jwt.audiences []string. When set, a token authenticates only if its aud claim intersects the configured list ("any match"; the same semantics as kubernetesTokenReview.audiences and Envoy's jwt_authn). When omitted (the default), the claim is not verified and existing AuthConfigs behave identically.

  • Enforced at the authentication phase, so a wrong-audience token returns 401 (and honours response.unauthenticated), not 403.
  • Applies to both the issuerUrl and jwksUrl paths. go-oidc already parses aud (JSON string or array) into []string, so this is a post-verify intersection check.
spec:
  authentication:
    "keycloak-users":
      jwt:
        issuerUrl: https://sso.corp.example/realms/prod
        issuer: https://sso.corp.example/realms/prod
        audiences:
        - my-api.io

This is the natural next field after #657's issuer: same shape (opt-in claim enforcement, default off, enforced in the verifier). It completes the mandatory-claim set (exp/nbf, iss, aud) and reaches parity with Envoy jwt_authn, Istio RequestAuthentication, and Authorino's own kubernetesTokenReview.

Describe alternatives you've considered

A pattern-matching / OPA authorization rule (e.g. "my-api" in auth.identity.aud) can approximate it, but it isn't the same behavior:

  • It runs in the authorization phase, so a wrong-audience token returns 403 instead of 401 and bypasses response.unauthenticated.
  • The CEL is a correctness trap: aud may be a JSON string or an array, so a naive rule silently passes on one of the two shapes.
  • It composes badly across multiple identity sources (needs per-source when conditioning to avoid rejecting non-JWT identities).

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions