Token exchange for cross-domain securityv1.0+
Use the OpenID Connect plugin to exchange tokens between different authorization servers or identity providers (IdPs). This is useful when you have APIs protected by different authorization servers, but you want to allow clients to access all of them with a single token.
In this scenario:
- The client is issued a token from AuthZ Server A.
- APIs are protected by AuthZ Server B.
- The OIDC plugin is configured to exchange tokens from Server A with Server B.
Here’s how token exchange works with the OIDC plugin:
sequenceDiagram
participant C as Client
(e.g. mobile app)
participant K as API Gateway
with OIDC plugin
participant A as Authorization server
(e.g. Keycloak)
participant U as Upstream
(backend service,
e.g. httpbin)
C->>K: Request with subject token
activate K
note over K: Validate subject token
(iss, exp, nbf)
K->>A: Token exchange request
activate A
A-->>K: Exchanged access token
deactivate A
K->>K: Validate exchanged token
K->>U: Proxy request with exchanged token
activate U
U-->>K: Response
deactivate U
K-->>C: Response
deactivate K
Prerequisites
- Two configured identity providers (IdPs) that support token exchange. This can be two separate IdPs, such as Okta and Keycloak, or two AuthZ servers (Server A, Server B) in the same IdP.
Environment variables
-
ISSUER: The issuer authentication URL for the authorization server that issued the token in the incoming request. For example, if you’re using Keycloak as your IdP, the issuer URL looks like this:http://localhost:8080/realms/example-realm. -
SUBJECT_TOKEN_ISSUER: The issuer authentication URL for the authorization server that you want to exchange tokens with. -
CLIENT_ID: The client ID that the plugin uses when it calls authenticated endpoints of the IdP (in this case, Server A). -
CLIENT_SECRET: The client secret needed to connect to your IdP (in this case, Server A).
Add this section to your kong.yaml configuration file:
_format_version: "3.0"
plugins:
- name: openid-connect
config:
issuer: ${{ env "DECK_ISSUER" }}
client_id:
- ${{ env "DECK_CLIENT_ID" }}
client_secret:
- ${{ env "DECK_CLIENT_SECRET" }}
client_auth:
- client_secret_post
auth_methods:
- bearer
token_exchange:
subject_token_issuers:
- issuer: ${{ env "DECK_SUBJECT_TOKEN_ISSUER" }}
conditions:
missing_audience:
has_audience:
missing_scopes:
has_scopes:
request:
empty_audience: false
scopes:
empty_scopes: false
audience:
Make the following request:
curl -i -X POST http://localhost:8001/plugins/ \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '
{
"name": "openid-connect",
"config": {
"issuer": "'$ISSUER'",
"client_id": [
"'$CLIENT_ID'"
],
"client_secret": [
"'$CLIENT_SECRET'"
],
"client_auth": [
"client_secret_post"
],
"auth_methods": [
"bearer"
],
"token_exchange": {
"subject_token_issuers": [
{
"issuer": "'$SUBJECT_TOKEN_ISSUER'",
"conditions": {
"missing_audience": null,
"has_audience": null,
"missing_scopes": null,
"has_scopes": null
}
}
],
"request": {
"empty_audience": false,
"scopes": null,
"empty_scopes": false,
"audience": null
}
}
},
"tags": []
}
'
echo "
apiVersion: configuration.konghq.com/v1
kind: KongClusterPlugin
metadata:
name: openid-connect
namespace: kong
annotations:
kubernetes.io/ingress.class: kong
konghq.com/tags: ''
labels:
global: 'true'
config:
issuer: '$ISSUER'
client_id:
- '$CLIENT_ID'
client_secret:
- '$CLIENT_SECRET'
client_auth:
- client_secret_post
auth_methods:
- bearer
token_exchange:
subject_token_issuers:
- issuer: '$SUBJECT_TOKEN_ISSUER'
conditions:
missing_audience:
has_audience:
missing_scopes:
has_scopes:
request:
empty_audience: false
scopes:
empty_scopes: false
audience:
plugin: openid-connect
" | kubectl apply -f -
Prerequisite: Configure your Personal Access Token
terraform {
required_providers {
konnect = {
source = "kong/konnect"
}
}
}
provider "konnect" {
personal_access_token = "$KONNECT_TOKEN"
server_url = "https://us.api.konghq.com/"
}
Add the following to your Terraform configuration to create a Konnect Gateway Plugin:
resource "konnect_gateway_plugin_openid_connect" "my_openid_connect" {
enabled = true
config = {
issuer = var.issuer
client_id = [var.client_id]
client_secret = [var.client_secret]
client_auth = ["client_secret_post"]
auth_methods = ["bearer"]
token_exchange = {
subject_token_issuers = [
{
issuer = var.subject_token_issuer
conditions = {
missing_audience =
has_audience =
missing_scopes =
has_scopes =
}
} ]
request = {
empty_audience = false
scopes =
empty_scopes = false
audience =
}
}
}
tags = []
control_plane_id = konnect_gateway_control_plane.my_konnect_cp.id
}
This example requires the following variables to be added to your manifest. You can specify values at runtime by setting TF_VAR_name=value.
variable "client_secret" {
type = string
}
Add this section to your kong.yaml configuration file:
_format_version: "3.0"
plugins:
- name: openid-connect
service: serviceName|Id
config:
issuer: ${{ env "DECK_ISSUER" }}
client_id:
- ${{ env "DECK_CLIENT_ID" }}
client_secret:
- ${{ env "DECK_CLIENT_SECRET" }}
client_auth:
- client_secret_post
auth_methods:
- bearer
token_exchange:
subject_token_issuers:
- issuer: ${{ env "DECK_SUBJECT_TOKEN_ISSUER" }}
conditions:
missing_audience:
has_audience:
missing_scopes:
has_scopes:
request:
empty_audience: false
scopes:
empty_scopes: false
audience:
Make sure to replace the following placeholders with your own values:
-
serviceName|Id: Theidornameof the service the plugin configuration will target.
Make the following request:
curl -i -X POST http://localhost:8001/services/{serviceName|Id}/plugins/ \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '
{
"name": "openid-connect",
"config": {
"issuer": "'$ISSUER'",
"client_id": [
"'$CLIENT_ID'"
],
"client_secret": [
"'$CLIENT_SECRET'"
],
"client_auth": [
"client_secret_post"
],
"auth_methods": [
"bearer"
],
"token_exchange": {
"subject_token_issuers": [
{
"issuer": "'$SUBJECT_TOKEN_ISSUER'",
"conditions": {
"missing_audience": null,
"has_audience": null,
"missing_scopes": null,
"has_scopes": null
}
}
],
"request": {
"empty_audience": false,
"scopes": null,
"empty_scopes": false,
"audience": null
}
}
},
"tags": []
}
'
Make sure to replace the following placeholders with your own values:
-
serviceName|Id: Theidornameof the service the plugin configuration will target.
echo "
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: openid-connect
namespace: kong
annotations:
kubernetes.io/ingress.class: kong
konghq.com/tags: ''
config:
issuer: '$ISSUER'
client_id:
- '$CLIENT_ID'
client_secret:
- '$CLIENT_SECRET'
client_auth:
- client_secret_post
auth_methods:
- bearer
token_exchange:
subject_token_issuers:
- issuer: '$SUBJECT_TOKEN_ISSUER'
conditions:
missing_audience:
has_audience:
missing_scopes:
has_scopes:
request:
empty_audience: false
scopes:
empty_scopes: false
audience:
plugin: openid-connect
" | kubectl apply -f -
Next, apply the KongPlugin resource by annotating the service resource:
kubectl annotate -n kong service SERVICE_NAME konghq.com/plugins=openid-connect
Prerequisite: Configure your Personal Access Token
terraform {
required_providers {
konnect = {
source = "kong/konnect"
}
}
}
provider "konnect" {
personal_access_token = "$KONNECT_TOKEN"
server_url = "https://us.api.konghq.com/"
}
Add the following to your Terraform configuration to create a Konnect Gateway Plugin:
resource "konnect_gateway_plugin_openid_connect" "my_openid_connect" {
enabled = true
config = {
issuer = var.issuer
client_id = [var.client_id]
client_secret = [var.client_secret]
client_auth = ["client_secret_post"]
auth_methods = ["bearer"]
token_exchange = {
subject_token_issuers = [
{
issuer = var.subject_token_issuer
conditions = {
missing_audience =
has_audience =
missing_scopes =
has_scopes =
}
} ]
request = {
empty_audience = false
scopes =
empty_scopes = false
audience =
}
}
}
tags = []
control_plane_id = konnect_gateway_control_plane.my_konnect_cp.id
service = {
id = konnect_gateway_service.my_service.id
}
}
This example requires the following variables to be added to your manifest. You can specify values at runtime by setting TF_VAR_name=value.
variable "client_secret" {
type = string
}
Add this section to your kong.yaml configuration file:
_format_version: "3.0"
plugins:
- name: openid-connect
route: routeName|Id
config:
issuer: ${{ env "DECK_ISSUER" }}
client_id:
- ${{ env "DECK_CLIENT_ID" }}
client_secret:
- ${{ env "DECK_CLIENT_SECRET" }}
client_auth:
- client_secret_post
auth_methods:
- bearer
token_exchange:
subject_token_issuers:
- issuer: ${{ env "DECK_SUBJECT_TOKEN_ISSUER" }}
conditions:
missing_audience:
has_audience:
missing_scopes:
has_scopes:
request:
empty_audience: false
scopes:
empty_scopes: false
audience:
Make sure to replace the following placeholders with your own values:
-
routeName|Id: Theidornameof the route the plugin configuration will target.
Make the following request:
curl -i -X POST http://localhost:8001/routes/{routeName|Id}/plugins/ \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '
{
"name": "openid-connect",
"config": {
"issuer": "'$ISSUER'",
"client_id": [
"'$CLIENT_ID'"
],
"client_secret": [
"'$CLIENT_SECRET'"
],
"client_auth": [
"client_secret_post"
],
"auth_methods": [
"bearer"
],
"token_exchange": {
"subject_token_issuers": [
{
"issuer": "'$SUBJECT_TOKEN_ISSUER'",
"conditions": {
"missing_audience": null,
"has_audience": null,
"missing_scopes": null,
"has_scopes": null
}
}
],
"request": {
"empty_audience": false,
"scopes": null,
"empty_scopes": false,
"audience": null
}
}
},
"tags": []
}
'
Make sure to replace the following placeholders with your own values:
-
routeName|Id: Theidornameof the route the plugin configuration will target.
echo "
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: openid-connect
namespace: kong
annotations:
kubernetes.io/ingress.class: kong
konghq.com/tags: ''
config:
issuer: '$ISSUER'
client_id:
- '$CLIENT_ID'
client_secret:
- '$CLIENT_SECRET'
client_auth:
- client_secret_post
auth_methods:
- bearer
token_exchange:
subject_token_issuers:
- issuer: '$SUBJECT_TOKEN_ISSUER'
conditions:
missing_audience:
has_audience:
missing_scopes:
has_scopes:
request:
empty_audience: false
scopes:
empty_scopes: false
audience:
plugin: openid-connect
" | kubectl apply -f -
Next, apply the KongPlugin resource by annotating the httproute or ingress resource:
kubectl annotate -n kong httproute konghq.com/plugins=openid-connect
kubectl annotate -n kong ingress konghq.com/plugins=openid-connect
Prerequisite: Configure your Personal Access Token
terraform {
required_providers {
konnect = {
source = "kong/konnect"
}
}
}
provider "konnect" {
personal_access_token = "$KONNECT_TOKEN"
server_url = "https://us.api.konghq.com/"
}
Add the following to your Terraform configuration to create a Konnect Gateway Plugin:
resource "konnect_gateway_plugin_openid_connect" "my_openid_connect" {
enabled = true
config = {
issuer = var.issuer
client_id = [var.client_id]
client_secret = [var.client_secret]
client_auth = ["client_secret_post"]
auth_methods = ["bearer"]
token_exchange = {
subject_token_issuers = [
{
issuer = var.subject_token_issuer
conditions = {
missing_audience =
has_audience =
missing_scopes =
has_scopes =
}
} ]
request = {
empty_audience = false
scopes =
empty_scopes = false
audience =
}
}
}
tags = []
control_plane_id = konnect_gateway_control_plane.my_konnect_cp.id
route = {
id = konnect_gateway_route.my_route.id
}
}
This example requires the following variables to be added to your manifest. You can specify values at runtime by setting TF_VAR_name=value.
variable "client_secret" {
type = string
}