Summary
Add support for OAuth 2.0 Token Exchange (RFC 8693) as a new grant type. This enables trusted services (e.g., websites with existing LDAP/SSO authentication) to exchange a user identity assertion for an AuthGate access token — without requiring the user to log in to AuthGate separately.
Motivation
Organizations that already authenticate users via LDAP or other identity providers face a UX problem: if they also need AuthGate tokens for downstream services, users are forced to log in twice. Token Exchange solves this by allowing a trusted backend service to request tokens on behalf of an already-authenticated user.
Proposed Flow
User Website (LDAP) AuthGate
│ │ │
│── LDAP Login ────►│ │
│◄── Authenticated ─│ │
│ │ │
│ │── POST /oauth/token ────►│
│ │ grant_type= │
│ │ urn:ietf:params:oauth: │
│ │ grant-type:token-exchange │
│ │ subject_token=<JWT> │
│ │ client_id + client_secret │
│ │ │
│ │ │── Verify client identity ✓
│ │ │── Verify subject_token signature ✓
│ │ │── Lookup/create user ✓
│ │ │── Issue access_token
│ │ │
│ │◄── access_token ─────│
│◄── Done ──────────│ │
Request Format
POST /oauth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=eyJhbGciOiJSUzI1NiJ9...
&subject_token_type=urn:ietf:params:oauth:token-type:jwt
&scope=read write
| Parameter |
Description |
grant_type |
Fixed: urn:ietf:params:oauth:grant-type:token-exchange |
subject_token |
JWT signed by the trusted service, representing user identity |
subject_token_type |
urn:ietf:params:oauth:token-type:jwt |
scope |
(Optional) Requested scopes, must be subset of client's allowed scopes |
resource |
(Optional) Target resource URI |
audience |
(Optional) Target service identifier |
Subject Token (JWT from Trusted Service)
The trusted service signs a short-lived JWT with a pre-shared secret or registered public key:
{
"iss": "https://your-website.com",
"sub": "user123",
"aud": "https://authgate.example.com",
"exp": 1711000090,
"iat": 1711000060,
"email": "user@example.com",
"name": "John Doe"
}
Key constraints:
- Very short TTL (30–60 seconds) to prevent replay
aud must match AuthGate's base URL
iss must be a registered trusted issuer on the OAuth client
Response Format
{
"access_token": "eyJhbGciOiJIUzI1NiJ9...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}
Validation Steps
- Verify client identity —
client_id + client_secret (must be confidential client with EnableTokenExchange=true)
- Verify subject_token
- Validate JWT signature against client's registered JWKS/shared secret
- Check
iss is in the client's trusted issuer whitelist
- Check
aud matches AuthGate's base URL
- Check
exp is not expired (enforce max TTL, e.g., 60s)
- Lookup or create user — Find user by
sub claim; optionally auto-create if configured
- Validate scopes — Requested scopes must be subset of client's registered scopes
- Issue access token — Bound to the resolved user identity
Security Considerations
| Layer |
Protection |
| Client authentication |
client_secret or HMAC ensures only trusted services can call |
| Subject token signature |
Prevents forging user identity (requires signing key) |
| Short-lived subject token |
30–60s expiry minimizes replay window |
| Audience check |
Ensures token is intended for AuthGate, not another service |
| Issuer whitelist |
AuthGate only accepts tokens from registered issuers |
| Scope restriction |
Client can only request its allowed scopes |
| Audit logging |
Full trail of which client exchanged tokens for which user |
Important limitation: Token Exchange prevents external/MITM attacks, but a trusted service holding the signing key can still impersonate any user. This is inherent to all delegation models. Mitigations include scope restrictions, audit logging, and short token lifetimes.
Implementation Plan
Model Changes
OAuthApplication: Add EnableTokenExchange bool field and TrustedIssuers (JSON array of allowed issuer URLs) and IssuerSecret or IssuerJWKS (for verifying subject tokens)
Handler Changes
internal/handlers/token.go: Add handleTokenExchangeGrant case in the token endpoint switch
Service Changes
internal/services/token.go: Add IssueTokenExchangeToken() method with full validation logic
Audit
- Add
TOKEN_EXCHANGE event type to audit logging
Configuration
- No new global env vars required — configuration is per-client (on
OAuthApplication)
Admin UI
- Update client edit form to include Token Exchange toggle, trusted issuers, and issuer secret/JWKS fields
References
Summary
Add support for OAuth 2.0 Token Exchange (RFC 8693) as a new grant type. This enables trusted services (e.g., websites with existing LDAP/SSO authentication) to exchange a user identity assertion for an AuthGate access token — without requiring the user to log in to AuthGate separately.
Motivation
Organizations that already authenticate users via LDAP or other identity providers face a UX problem: if they also need AuthGate tokens for downstream services, users are forced to log in twice. Token Exchange solves this by allowing a trusted backend service to request tokens on behalf of an already-authenticated user.
Proposed Flow
Request Format
grant_typeurn:ietf:params:oauth:grant-type:token-exchangesubject_tokensubject_token_typeurn:ietf:params:oauth:token-type:jwtscoperesourceaudienceSubject Token (JWT from Trusted Service)
The trusted service signs a short-lived JWT with a pre-shared secret or registered public key:
{ "iss": "https://your-website.com", "sub": "user123", "aud": "https://authgate.example.com", "exp": 1711000090, "iat": 1711000060, "email": "user@example.com", "name": "John Doe" }Key constraints:
audmust match AuthGate's base URLissmust be a registered trusted issuer on the OAuth clientResponse Format
{ "access_token": "eyJhbGciOiJIUzI1NiJ9...", "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", "token_type": "Bearer", "expires_in": 3600, "scope": "read write" }Validation Steps
client_id+client_secret(must be confidential client withEnableTokenExchange=true)issis in the client's trusted issuer whitelistaudmatches AuthGate's base URLexpis not expired (enforce max TTL, e.g., 60s)subclaim; optionally auto-create if configuredSecurity Considerations
client_secretor HMAC ensures only trusted services can callImportant limitation: Token Exchange prevents external/MITM attacks, but a trusted service holding the signing key can still impersonate any user. This is inherent to all delegation models. Mitigations include scope restrictions, audit logging, and short token lifetimes.
Implementation Plan
Model Changes
OAuthApplication: AddEnableTokenExchange boolfield andTrustedIssuers(JSON array of allowed issuer URLs) andIssuerSecretorIssuerJWKS(for verifying subject tokens)Handler Changes
internal/handlers/token.go: AddhandleTokenExchangeGrantcase in the token endpoint switchService Changes
internal/services/token.go: AddIssueTokenExchangeToken()method with full validation logicAudit
TOKEN_EXCHANGEevent type to audit loggingConfiguration
OAuthApplication)Admin UI
References