Skip to main content
Guides

Token Exchange (RFC 8693)

Resolve per-user credentials dynamically by calling an external Security Token Service (STS). Multiple callers with different identities share a single gatekeeper instance. Each request triggers a token exchange — or uses a cached token — scoped to the caller’s identity.

This guide covers gatekeeper configuration and the STS endpoint contract. Implement the STS endpoint yourself; gatekeeper is the client.

Prerequisites

  • CA certificate generated (CA Setup)
  • An STS endpoint that implements RFC 8693 token exchange (see STS Endpoint Requirements below)
  • Client credentials (client_id and client_secret) for authenticating gatekeeper to the STS

How It Works

  1. A request arrives at gatekeeper with a subject identity (via header or proxy auth username).
  2. Gatekeeper checks its cache for a valid token for that subject.
  3. On cache miss, gatekeeper sends an RFC 8693 POST to the STS with the subject token, client credentials, and optional resource/actor parameters.
  4. The STS returns an access_token (and optional expires_in).
  5. Gatekeeper caches the token and injects it into the upstream request.

Subject Identity Modes

Gatekeeper extracts the subject identity from one of two sources. The two modes are mutually exclusive.

Mode 1: Subject from Request Header

The subject identity is read from a named HTTP header on each request. Gatekeeper strips the header before forwarding upstream.

credentials:
  - host: api.github.com
    grant: github
    prefix: Bearer
    source:
      type: token-exchange
      endpoint: https://sts.example.com/token
      client_id: gk-client
      client_secret_env: STS_CLIENT_SECRET
      subject_header: X-Gatekeeper-Subject
      resource: https://api.github.com

The client includes the subject in each request:

curl --proxy http://127.0.0.1:9080 --cacert ca.crt \
  -H "X-Gatekeeper-Subject: alice@example.com" \
  https://api.github.com/user

Mode 2: Subject from Proxy Auth

The subject identity is extracted from the username in proxy authentication credentials. No request headers are modified.

credentials:
  - host: api.github.com
    grant: github
    prefix: Bearer
    source:
      type: token-exchange
      endpoint: https://sts.example.com/token
      client_id: gk-client
      client_secret_env: STS_CLIENT_SECRET
      subject_from: proxy-auth
      resource: https://api.github.com

The client encodes the subject in the proxy URL. Percent-encode @ as %40:

export HTTP_PROXY="http://alice%40example.com:proxypass@127.0.0.1:9080"
curl --cacert ca.crt https://api.github.com/user

Configuration Reference

FieldRequiredDefaultDescription
endpointYesSTS token endpoint URL
client_idYesOAuth client ID for HTTP Basic auth to STS
client_secretOne ofClient secret (literal value)
client_secret_envOne ofEnvironment variable containing the client secret
subject_headerOne ofRequest header to extract subject from (stripped before forwarding)
subject_fromOne ofSet to proxy-auth to extract subject from proxy auth username
subject_token_typeNourn:ietf:params:oauth:token-type:access_tokenToken type URI for the subject token
resourceNoTarget resource URI sent to the STS
actor_token_fromNoSet to proxy-auth-password to forward the proxy auth password as actor token
actor_token_typeNourn:ietf:params:oauth:token-type:access_tokenToken type URI for the actor token

Actor Token Forwarding

By default, subject identities are self-asserted — any caller can claim any identity. In shared environments, use actor token forwarding to let the STS verify caller identity.

Configure gatekeeper to forward the proxy auth password as the RFC 8693 actor_token:

credentials:
  - host: api.github.com
    grant: github
    prefix: Bearer
    source:
      type: token-exchange
      endpoint: https://sts.example.com/token
      client_id: gk-client
      client_secret_env: STS_CLIENT_SECRET
      subject_from: proxy-auth
      actor_token_from: proxy-auth-password
      resource: https://api.github.com

Each caller uses a unique API key as the proxy auth password:

HTTP_PROXY=http://alice%40example.com:ak_alice_xxxxx@127.0.0.1:9080

Gatekeeper sends both subject_token=alice@example.com and actor_token=ak_alice_xxxxx to the STS. The STS validates that the API key belongs to Alice before issuing tokens.

When actor_token_from is configured on any credential, gatekeeper requires all clients to provide Basic proxy auth with a non-empty password. The password is not checked against a static value — it is forwarded to the STS.

Caching Behavior

Gatekeeper caches tokens per (subject_token, actor_token) pair:

  • If expires_in is returned by the STS, the token is cached until expiry.
  • If expires_in is 0 or omitted, a default TTL of 5 minutes is applied.
  • Concurrent requests for the same subject are coalesced into a single STS call via singleflight.
  • Expired entries are evicted lazily on the next exchange.
  • There is no proactive refresh. When a cached token expires, the next request triggers a new exchange.

For high-throughput scenarios, set expires_in to a reasonable TTL (e.g., 3600 for one hour) to avoid per-request STS calls.

STS Endpoint Requirements

Gatekeeper sends a POST with Content-Type: application/x-www-form-urlencoded and HTTP Basic authentication.

Request Format

POST /token HTTP/1.1
Host: sts.example.com
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&subject_token=alice%40example.com&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&resource=https%3A%2F%2Fapi.github.com

Success Response (HTTP 200)

{
  "access_token": "gho_exchanged_abc123",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600
}
FieldTypeRequiredDescription
access_tokenstringYesThe token gatekeeper injects upstream
issued_token_typestringNoToken type URI of the issued token
token_typestringNoInformational; gatekeeper uses its own prefix config
expires_inintNoTTL in seconds. Defaults to 300 if omitted

access_token must be non-empty. Gatekeeper treats an empty value as an error.

Error Response (non-200)

Gatekeeper treats any non-200 status as a failure and returns HTTP 502 to the client. Use standard OAuth error format:

{
  "error": "invalid_grant",
  "error_description": "Subject token is expired or revoked"
}

Note: Do not echo actor_token in error responses. Gatekeeper logs up to 200 bytes of STS error bodies, so sensitive values would appear in proxy logs.

Implementation Checklist

  • Accept POST with Content-Type: application/x-www-form-urlencoded
  • Validate HTTP Basic auth credentials (client_id / client_secret)
  • Validate grant_type is exactly urn:ietf:params:oauth:grant-type:token-exchange
  • Extract subject_token — the user/caller identity
  • Read resource if present — the target API
  • Look up or mint an access token for the given subject and resource
  • Return JSON with a non-empty access_token
  • Set expires_in to enable caching
  • Return non-200 for invalid/expired/unknown subjects
  • Handle concurrent requests (idempotency or internal locking)
  • (Optional) Validate actor_token against subject_token to prevent impersonation

Testing

Test your STS endpoint with curl:

curl -X POST https://sts.example.com/token \
  -u "gk-client:your-secret" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token=alice&subject_token_type=urn:ietf:params:oauth:token-type:access_token&resource=https://api.github.com"

Then test through the proxy:

curl --cacert ca.crt --proxy http://127.0.0.1:9080 \
  -H "X-Gatekeeper-Subject: alice" \
  https://api.github.com/user

The proxy log shows credential injection with the grant name.

Next Steps