P3.5: Implement RFC 8693 OAuth2 Token Exchange endpoint #9

Closed
opened 2026-09-08 03:56:16 +00:00 by rock · 0 comments
Owner

Summary

Gateway exposes POST /auth/exchange implementing RFC 8693 token exchange. A service presents a user's JWT + its own credentials to get a new token scoped to a specific downstream, carrying both identities.

Why

When portfolio-agent calls memory service on behalf of user "rock", memory needs to know:

  • Who: rock (the subject)
  • Via whom: portfolio-agent (the actor)
  • What scope: memory:read only (not the full service account permissions)

Without token exchange, service accounts use their own fixed-scope JWT. No per-user delegation.

Request

POST /auth/exchange
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=<user JWT>
subject_token_type=urn:ietf:params:oauth:token-type:access_token
client_id=portfolio-agent
client_secret=<service secret>
scope=memory:read
resource=poimen-memory

Response (new JWT claims)

{
  "sub": "rock",
  "azp": "portfolio-agent",
  "act": {"sub": "portfolio-agent"},
  "roles": ["memory:read"],
  "aud": "poimen-memory",
  "memory_projects": ["homelab", "portfolio"]
}

Implementation options

  1. Authentik-native: If Authentik supports RFC 8693, proxy to Authentik token endpoint (preferred)
  2. Gateway-minted: Gateway validates both tokens, mints a new JWT signed with a gateway-specific key. Downstream validates against gateway JWKS. More control, more responsibility.

Investigation needed

  • Does Authentik support urn:ietf:params:oauth:grant-type:token-exchange?
  • If yes, what configuration is needed?
  • If no, implement option 2 (gateway-minted exchange tokens)

Acceptance Criteria

  • Service presents user token + own credentials, gets scoped token
  • New token has act claim (RFC 8693 actor claim)
  • New token is scoped down (cannot escalate permissions)
  • Subject token validation (expired/invalid subject token rejected)
  • Actor credentials validation (bad client_secret rejected)
  • Scope narrowing: requested scope must be subset of actor's roles
  • Unit tests for all validation paths
  • Integration test: portfolio-agent exchanges rock's token for memory-scoped token

Depends on

  • P3.2 (Authentik provider config)
  • P3.4 (identity headers — exchanged token still produces correct headers)

Review checklist

  • Scope escalation impossible (new token cannot have more permissions than either party)
  • Subject token expiry checked before exchange
  • Exchanged token has shorter TTL than subject token
  • act claim present and correct
  • Audit log entry for every exchange (who exchanged what for whom)
## Summary Gateway exposes `POST /auth/exchange` implementing RFC 8693 token exchange. A service presents a user's JWT + its own credentials to get a new token scoped to a specific downstream, carrying both identities. ## Why When portfolio-agent calls memory service on behalf of user "rock", memory needs to know: - **Who**: rock (the subject) - **Via whom**: portfolio-agent (the actor) - **What scope**: memory:read only (not the full service account permissions) Without token exchange, service accounts use their own fixed-scope JWT. No per-user delegation. ## Request ```http POST /auth/exchange Content-Type: application/x-www-form-urlencoded grant_type=urn:ietf:params:oauth:grant-type:token-exchange subject_token=<user JWT> subject_token_type=urn:ietf:params:oauth:token-type:access_token client_id=portfolio-agent client_secret=<service secret> scope=memory:read resource=poimen-memory ``` ## Response (new JWT claims) ```json { "sub": "rock", "azp": "portfolio-agent", "act": {"sub": "portfolio-agent"}, "roles": ["memory:read"], "aud": "poimen-memory", "memory_projects": ["homelab", "portfolio"] } ``` ## Implementation options 1. **Authentik-native**: If Authentik supports RFC 8693, proxy to Authentik token endpoint (preferred) 2. **Gateway-minted**: Gateway validates both tokens, mints a new JWT signed with a gateway-specific key. Downstream validates against gateway JWKS. More control, more responsibility. ## Investigation needed - [ ] Does Authentik support `urn:ietf:params:oauth:grant-type:token-exchange`? - [ ] If yes, what configuration is needed? - [ ] If no, implement option 2 (gateway-minted exchange tokens) ## Acceptance Criteria - [ ] Service presents user token + own credentials, gets scoped token - [ ] New token has `act` claim (RFC 8693 actor claim) - [ ] New token is scoped down (cannot escalate permissions) - [ ] Subject token validation (expired/invalid subject token rejected) - [ ] Actor credentials validation (bad client_secret rejected) - [ ] Scope narrowing: requested scope must be subset of actor's roles - [ ] Unit tests for all validation paths - [ ] Integration test: portfolio-agent exchanges rock's token for memory-scoped token ## Depends on - P3.2 (Authentik provider config) - P3.4 (identity headers — exchanged token still produces correct headers) ## Review checklist - [ ] Scope escalation impossible (new token cannot have more permissions than either party) - [ ] Subject token expiry checked before exchange - [ ] Exchanged token has shorter TTL than subject token - [ ] `act` claim present and correct - [ ] Audit log entry for every exchange (who exchanged what for whom)
rock added this to the Phase 3: OAuth2 Token Exchange & Identity Propagation milestone 2026-09-08 03:56:16 +00:00
rock self-assigned this 2026-09-08 03:56:16 +00:00
rock added this to the (deleted) project 2026-09-08 04:37:13 +00:00
rock closed this issue 2026-09-08 04:45:09 +00:00
Sign in to join this conversation.