Skip to content

OAuth access tokens: aud defaults to the issuer — adopt RFC 8707 resource indicators so it names the target resource server #199

@saucam

Description

@saucam

Problem

When the credential service issues a token without an explicit audience, aud falls back to the issuer URL — so by default aud == iss. That is a present-but-nominal placeholder, not a real audience. Per RFC 7519 §4.1.3, aud identifies the recipients the token is intended for (the target resource server), not the party that minted it (that is iss).

// internal/service/credential.go:375-382
// JWT-SVID §3 requires `aud` to be present on every issued token. Default
// to the issuer URL when no audience was supplied so tokens remain
// interoperable with spec-compliant verifiers (e.g., pkg/authjwt).
aud := req.Audience
if len(aud) == 0 {
    aud = []string{s.issuer}
}
_ = token.Set(jwt.AudienceKey, aud)

The root cause is that the OAuth token endpoint gives a caller no way to name the target resource. TokenInput.Body (internal/handler/oauth.go:26-65) exposes no resource or audience parameter on any grant, including token-exchange (RFC 8693). So at mint time the AS does not know the intended RS, and the JWT-SVID §3 "aud MUST be present" rule forces a non-empty fallback — the issuer.

Impact

  • Semantically wrong: the token effectively says "intended for the issuer," which is never the real relying party.
  • Latent rejection risk: a strict resource server that validates aud == <itself> per RFC 7519 §4.1.3 would reject a aud = issuer token. Today these tokens validate only because audience checking is optional — pkg/authjwt enforces aud exclusively when a deployment configures an expected Audience (pkg/authjwt/verifier.go:58-60, 163). In effect the tokens lean on lax or issuer-configured verifiers.
  • It makes the single-audience direction we discussed on the WIMSE thread nominal rather than meaningful — there is no real audience to scope down to.

Proposed change

Adopt RFC 8707 — Resource Indicators for OAuth 2.0:

  1. Accept a resource parameter (and optionally audience) on /oauth2/token and the token-exchange grant.
  2. Validate it against an allowed-resource list (per tenant/client).
  3. Set aud to the requested resource(s) instead of the issuer.
  4. Decide the no-resource behaviour explicitly — reject, or fall back to a configured default — rather than silently substituting the issuer.

Scope this to the OAuth access-token profile; JWT-SVID / WIMSE assertion paths keep their own audience semantics.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestspec-complianceDeviation from SPIFFE/WIMSE/JWT-SVID specs

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions