Skip to content

OIDC proxy does not enforce scope claims from incoming JWTs (--oidc-scopes only advertises, not validates) #5284

@amirejaz

Description

@amirejaz

Summary

The --oidc-scopes flag on thv run is documented (and named) to suggest it enforces required scopes on incoming JWT tokens. In practice it only advertises scopes in the OAuth2 discovery document (RFC 9728 /.well-known/oauth-protected-resource) and in the WWW-Authenticate response header. It does not validate that incoming tokens carry those scopes.

Expected behaviour

When a proxy is started with --oidc-scopes required-scope, any JWT that does not contain required-scope in its scope claim should be rejected with 401 Unauthorized (or 403 Forbidden).

Actual behaviour

validateClaims in pkg/auth/token.go only checks issuer, audience, and expiry. The v.scopes field is never consulted during token validation — it is only used to populate:

  • The scope field in the WWW-Authenticate response header (buildWWWAuthenticate)
  • The scopes_supported field in the OAuth2 resource metadata document (NewAuthInfoHandler)

Any token with valid issuer, audience, and expiry is accepted regardless of its scope claim.

Impact

A downstream application that depends on thv enforcing OAuth scopes for access control is silently unprotected. Tokens issued without the required scope are accepted as if they were fully authorized.

Relevant code

pkg/auth/token.govalidateClaims function:

func (v *TokenValidator) validateClaims(claims jwt.MapClaims) error {
    // Validates: issuer, audience, expiry only
    // v.scopes is never checked here
    ...
    return nil
}

Suggested fix

Add scope claim validation to validateClaims:

if len(v.scopes) > 0 {
    scopeClaim, _ := claims["scope"].(string)
    tokenScopes := strings.Fields(scopeClaim)
    for _, required := range v.scopes {
        found := slices.Contains(tokenScopes, required)
        if !found {
            return ErrInsufficientScope
        }
    }
}

Discovery context

Found while implementing DAST adversarial tests for scope validation in the enterprise platform. The test (missing_scope in the token variation matrix) cannot be automated end-to-end because the proxy accepts all valid OIDC tokens regardless of scope.

Metadata

Metadata

Assignees

No one assigned

    Labels

    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