OAuth 2.0 Token Exchange Authentication
This feature is currently in early access.
The OAuth 2.0 Token Exchange middleware exchanges an incoming subject token for a new, downstream-scoped access token before forwarding the request. It supports the RFC 8693 token-exchange grant and the Microsoft Entra ID On-Behalf-Of flow. Exchanged tokens can be cached using an external KV store, so Hub API Gateway does not call the authorization server on every request.
Configuration Example
- Middleware RFC 8693
- Middleware Entra ID (client secret)
- Middleware Entra ID (certificate)
- Kubernetes Secret
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-token-exchange
spec:
plugin:
oAuthTokenExchange:
method: rfc8693
url: https://tenant.auth0.com/oauth/token
clientID: urn:k8s:secret:my-secret:clientID
clientSecret: urn:k8s:secret:my-secret:clientSecret
rfc8693:
subjectTokenType: "urn:ietf:params:oauth:token-type:jwt"
requestedTokenType: "urn:ietf:params:oauth:token-type:access_token"
audience: https://api.example.com
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-token-exchange-entraid
spec:
plugin:
oAuthTokenExchange:
method: entraID
url: https://login.microsoftonline.com/YOUR-TENANT-ID/oauth2/v2.0/token
clientID: urn:k8s:secret:my-secret:clientID
clientSecret: urn:k8s:secret:my-secret:clientSecret
entraID:
scopes:
- api://downstream-app/.default
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-token-exchange-entraid-cert
spec:
plugin:
oAuthTokenExchange:
method: entraID
url: https://login.microsoftonline.com/YOUR-TENANT-ID/oauth2/v2.0/token
clientID: urn:k8s:secret:my-secret:clientID
entraID:
scopes:
- api://downstream-app/.default
clientCertificate:
certificate: urn:k8s:secret:my-secret:certificate
privateKey: urn:k8s:secret:my-secret:privateKey
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: my-secret
stringData:
clientID: my-oauth-client-id
clientSecret: mypasswd
Keycloak requires rfc8693.subjectTokenType: "urn:ietf:params:oauth:token-type:access_token". Use this value instead of jwt when the authorization server is Keycloak.
Configuration Options
| Field | Description | Default | Required |
|---|---|---|---|
claims | Defines the claims to validate in order to authorize the request. Claims are evaluated against the exchanged access token returned by the authorization server, not against the subject token presented by the client. The claims option can only be used with JWT-formatted tokens. (More information in the claims section) | "" | No |
clientConfig.tls.ca | PEM-encoded certificate bundle or a URN referencing a secret containing the certificate bundle used to establish a TLS connection with the authorization server (More information in the clientConfig section) | "" | No |
clientConfig.tls.cert | PEM-encoded certificate or a URN referencing a secret containing the certificate used to establish a TLS connection with the authorization server (More information in the clientConfig section) | "" | No |
clientConfig.tls.key | PEM-encoded key or a URN referencing a secret containing the key used to establish a TLS connection with the authorization server. (More information in the clientConfig section) | "" | No |
clientConfig.tls.insecureSkipVerify | Disables TLS certificate verification when communicating with the authorization server. Useful for testing purposes but strongly discouraged for production. (More information in the clientConfig section) | "" | No |
clientConfig.timeoutSeconds | Defines the time before giving up requests to the authorization server. | 5 | No |
clientConfig.maxRetries | Defines the number of retries for requests to the authorization server that fail. | 3 | No |
clientID | Defines the client identifier used to authenticate to the authorization server. Required by entraID; required by rfc8693 when the authorization server needs client authentication. | "" | No |
clientSecret | Defines the client secret used to authenticate to the authorization server. For method: entraID, either clientSecret or entraID.clientCertificate must be set. More information in the Storing secret values in Kubernetes secrets section. | "" | No |
entraID.clientCertificate.certificate | Defines the PEM-encoded client certificate used to sign the client assertion sent to Microsoft Entra ID. Only RSA certificates are supported. Required when entraID.clientCertificate is set. | "" | No |
entraID.clientCertificate.privateKey | Defines the PEM-encoded RSA private key matching entraID.clientCertificate.certificate. Required when entraID.clientCertificate is set. | "" | No |
entraID.scopes | Defines the target scopes requested from Microsoft Entra ID (for example, api://downstream-app/.default). Required when method: entraID. | [] | No |
forwardHeaders | Defines the HTTP headers to add to requests and populates them with values extracted from the exchanged access token claims. Claims to be forwarded that are not found in the JWT result in empty headers. The forwardHeaders option can only be used with JWT-formatted tokens. | [] | No |
method | Selects the token-exchange grant. One of rfc8693 or entraID. Only one of rfc8693 or entraID can be configured at the same time, matching the selected method. | rfc8693 | No |
rfc8693.actorToken | Defines the RFC 8693 actor_token sent when the exchange is performed on behalf of an actor. Requires rfc8693.actorTokenType. | "" | No |
rfc8693.actorTokenType | Defines the RFC 8693 actor_token_type matching rfc8693.actorToken. | "" | No |
rfc8693.audience | Defines the RFC 8693 audience requested for the exchanged token. | "" | No |
rfc8693.clientAuthMethod | Pins how the client credentials are sent to the authorization server: in_header for HTTP Basic authentication, or in_params in the request body. Leave empty to auto-detect the method, which costs an extra request to the authorization server on every exchange when it does not accept HTTP Basic. | "" | No |
rfc8693.requestedTokenType | Defines the RFC 8693 requested_token_type (for example, urn:ietf:params:oauth:token-type:access_token). | "" | No |
rfc8693.resource | Defines the RFC 8693 resource requested for the exchanged token. | "" | No |
rfc8693.scopes | Defines the OAuth scopes requested from the authorization server. Sent as a space-separated list in the token request body. | [] | No |
rfc8693.subjectTokenType | Defines the RFC 8693 subject_token_type of the incoming token (for example, urn:ietf:params:oauth:token-type:jwt). Required when method: rfc8693. | "" | No |
store.keyPrefix | Defines the prefix of the key for the entries that store the exchanged tokens. An exchanged token is cached only when the authorization server returns expires_in. | "" | No |
store.secret | Defines the secret used to encrypt cached tokens at rest. Must be 16, 24, or 32 characters long. Required when store is set. | "" | No |
store.redis.endpoints | Endpoints of the Redis instances to connect to (example: redis.traefik-hub.svc.cluster.local:6379) | "" | Yes |
store.redis.username | The username Traefik Hub will use to connect to Redis | "" | No |
store.redis.password | The password Traefik Hub will use to connect to Redis | "" | No |
store.redis.database | The database Traefik Hub will use to store information (default: 0) | "" | No |
store.redis.cluster | Enable Redis Cluster | "" | No |
store.redis.tls.caBundle | Custom CA bundle | "" | No |
store.redis.tls.cert | TLS certificate | "" | No |
store.redis.tls.key | TLS key | "" | No |
store.redis.tls.insecureSkipVerify | Allow skipping the TLS verification | "" | No |
store.redis.sentinel.masterSet | Name of the set of main nodes to use for main selection. Required when using Sentinel. | "" | No |
store.redis.sentinel.username | Username to use for sentinel authentication (can be different from username) | "" | No |
store.redis.sentinel.password | Password to use for sentinel authentication (can be different from password) | "" | No |
tokenSource.header | Defines the header name containing the subject token sent by the client. Defaults to reading a Bearer token from the Authorization header when tokenSource is not set. | "Authorization" | No |
tokenSource.headerAuthScheme | Defines the scheme when using Authorization as header name. Check out the Authorization header documentation. | "Bearer" | No |
tokenSource.query | Defines the query parameter name containing the subject token sent by the client. | "" | No |
tokenSource.cookie | Defines the cookie name containing the subject token sent by the client. | "" | No |
url | Defines the token endpoint of the authorization server. For entraID, this is the tenant /oauth2/v2.0/token endpoint. | "" | Yes |
usernameClaim | Defines the claim that will be evaluated to populate the clientusername in the access logs. Evaluated against the exchanged access token, not the subject token. The usernameClaim option can only be used with JWT-formatted tokens. | "" | No |
When the authorization server returns an access token that is not JWT-formatted, claims, forwardHeaders, and usernameClaim cannot be used, and the middleware fails the request if any of them are configured.
Storing secret values in Kubernetes secrets
It is possible to reference Kubernetes secrets defined in the same namespace as the Middleware. The reference to a Kubernetes secret takes the form of a URN:
urn:k8s:secret:[name]:[valueKey]
claims
Syntax
The following functions are supported in claims:
| Function | Description | Example |
|---|---|---|
| Equals | Validates the equality of the value in key with value. | Equals(`grp`, `admin`) |
| Prefix | Validates the value in key has the prefix of value. | Prefix(`referrer`, `http://example.com\`) |
| Contains (string) | Validates the value in key contains value. | Contains(`referrer`, `/foo/`) |
| Contains (array) | Validates the key array contains the value. | Contains(`areas`, `home`) |
| SplitContains | Validates the value in key contains the value once split by the separator. | SplitContains(`scope`, ` `, `writer`) |
| OneOf | Validates the key array contains one of the values. | OneOf(`areas`, `office`, `lab`) |
All functions can be joined by boolean operands. The supported operands are:
| Operand | Description | Example |
|---|---|---|
| && | Compares two functions and returns true only if both evaluate to true. | Equals(`grp`, `admin`) && Equals(`active`, `true`) |
| || | Compares two functions and returns true if either evaluate to true. | Equals(`grp`, `admin`) || Equals(`active`, `true`) |
| ! | Returns false if the function is true, otherwise returns true. | !Equals(`grp`, `testers`) |
All examples will return true for the following data structure:
{
"active": true,
"grp": "admin",
"scope": "reader writer deploy",
"referrer": "http://example.com/foo/bar",
"areas": [
"office",
"home"
]
}
Nested Claims
Nested claims are supported by using a . between keys. For example:
user.name
{
"active": true,
"grp": "admin",
"scope": "reader writer deploy",
"referrer": "http://example.com/foo/bar",
"areas": [
"office",
"home"
],
"user" {
"name": "John Snow",
"status": "undead"
}
}
John Snow
If the key contains a dot, the dot can be escaped using \.
If the key contains a \, it needs to be doubled \\.
clientConfig
Defines the configuration used to connect the API Gateway to a Third Party Software such as an Identity Provider.
clientConfig.tls
Storing secret values in Kubernetes secrets
When configuring the tls.ca, tls.cert, tls.key, it is possible to reference Kubernetes secrets defined in the same namespace as the Middleware.
The reference to a Kubernetes secret takes the form of a URN:
urn:k8s:secret:[name]:[valueKey]
- Middleware configuration
- Kubernetes TLS secret
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-token-exchange
spec:
plugin:
oAuthTokenExchange:
clientConfig:
tls:
ca: "urn:k8s:secret:tls:ca"
cert: "urn:k8s:secret:tls:cert"
key: "urn:k8s:secret:tls:key"
insecureSkipVerify: true
apiVersion: v1
kind: Secret
metadata:
name: tls
stringData:
ca: |-
-----BEGIN CERTIFICATE-----
MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV
BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl
Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt
aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3
DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl
EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j
RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE
apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP
Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX
ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ
98TwDIK/39WEB/V607As+KoYazQG8drorw==
-----END CERTIFICATE-----
cert: |-
-----BEGIN CERTIFICATE-----
MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV
BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl
Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt
aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3
DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl
EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j
RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE
apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP
Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX
ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ
98TwDIK/39WEB/V607As+KoYazQG8drorw==
-----END CERTIFICATE-----
key: |-
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIC8CsJ/B115S+JtR1/l3ZQwKA3XdXt9zLqusF1VXc/KloAoGCCqGSM49
AwEHoUQDQgAEpwUmRIZHFt8CdDHYm1ikScCScd2q6QVYXxJu+G3fQZ78ScGtN7fu
KXMnQqVjXVRAr8qUY8yipVKuMCepnPXScQ==
-----END EC PRIVATE KEY-----
store.redis
Connection parameters to your Redis server are attached to your Middleware deployment.
The following Redis modes are supported:
- Single instance mode
- Redis Cluster
- Redis Sentinel
For more information about Redis, we recommend the official Redis documentation.
If you use Redis in single instance mode or Redis Sentinel, you can configure the database field.
This value won't be taken into account if you use Redis Cluster (only database 0 is available).
In this case, a warning is displayed, and the value is ignored.
