Problem/Motivation

The module's declared per-environment agent principals authenticate over simple_oauth with the client_credentials grant. The site exposes RFC 8414 / OpenID Connect discovery and a JWKS, so an external OAuth resource server (for example the Drupal MCP Connector's protected-resource mode, or any RFC 9728-style gateway) can validate these tokens cryptographically. Two claim gaps stop it there:

  • No client identity claim. The access token carries neither azp nor client_id. A resource server that keys entitlement grants on the caller's client identity resolves it to nothing, and a grants table entitles the token to zero targets. Fail-closed, but it means a legitimately issued token can never be granted anything.
  • The audience is the client id, not a resource. aud is the consumer's client id (observed: "aud": "content-staging"). A protected resource that validates the audience against its own HTTPS resource identifier (RFC 8707 style) must refuse every token, because a client id can never match a resource URL.

Observed at 2.13.2: a token minted by the site validates by signature and issuer at an external resource server and is then refused on audience — correct fail-closed behavior, and structurally unable to become an allow.

Steps to reproduce

  1. Mint a token: curl -s -X POST https://example.com/oauth/token -d grant_type=client_credentials -d client_id=CLIENT -d client_secret=…
  2. Decode the payload: aud is the client id; there is no azp or client_id claim.
  3. Configure any audience-validating resource server against the site as issuer: the token can never carry the resource's identifier, and no claim identifies the client.

Proposed resolution

Mint the missing claims on the site's access tokens (simple_oauth provides claim alteration seams):

  • add client_id (and/or azp) with the consumer's client id;
  • optionally support an audience/resource value per consumer (or honor an RFC 8707 resource parameter on the token request) so aud can name the protected resource the token is intended for.

Alternatively, document that entitlement-filtered resource servers require an external issuer (e.g. Keycloak) that mints these claims, and that the site-as-issuer path is limited to first-party southbound authentication.

No security impact today: every observed outcome is a refusal. This is an enablement gap, not a bypass.

Comments

jmcerda created an issue. See original summary.

jmcerda’s picture

Issue summary: View changes
jmcerda’s picture

Status: Active » Fixed

Shipped in 2.14.0. hook_simple_oauth_private_claims_alter fills client_id and azp from the consumer identifier without overwriting values another alter already set. Scope honestly bounded as recorded: aud remains the consumer id (registered claim, set upstream with permittedFor()), so RFC 8707 resource audience support stays a simple_oauth change.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • jmcerda committed d01019fb on 1.x
    #3619398: drop empty-id guard PHPStan flags as dead
    

  • jmcerda committed 763d8712 on 1.x
    #3619398: emit client_id and azp on site-issued access tokens...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.