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.
Authentik-native: If Authentik supports RFC 8693, proxy to Authentik token endpoint (preferred)
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)
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Gateway exposes
POST /auth/exchangeimplementing 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:
Without token exchange, service accounts use their own fixed-scope JWT. No per-user delegation.
Request
Response (new JWT claims)
Implementation options
Investigation needed
urn:ietf:params:oauth:grant-type:token-exchange?Acceptance Criteria
actclaim (RFC 8693 actor claim)Depends on
Review checklist
actclaim present and correct