-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Add OAuth 2.0 Token Exchange (RFC 8693) support #112
Description
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
audmust match AuthGate's base URLissmust 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 withEnableTokenExchange=true) - Verify subject_token
- Validate JWT signature against client's registered JWKS/shared secret
- Check
issis in the client's trusted issuer whitelist - Check
audmatches AuthGate's base URL - Check
expis not expired (enforce max TTL, e.g., 60s)
- Lookup or create user — Find user by
subclaim; 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: 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 switch
Service Changes
internal/services/token.go: AddIssueTokenExchangeToken()method with full validation logic
Audit
- Add
TOKEN_EXCHANGEevent 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