DIRECTORY_ID=$(curl -X POST "https://us.api.konghq.com/v2/directories" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"name": "kong-identity-directory",
"description": "Directory for this tutorial",
"allow_all_control_planes": true
}' | jq -r ".id"
)Dynamically set host based on the authenticated Principal with Datakit
Store the backend address as metadata on a Kong Identity Principal and link each OAuth client to its Principal with an auth_server_client identity.
Configure the OpenID Connect plugin with principals.enabled set to true so it hydrates the Principal after verifying the token.
Then configure the Datakit plugin to read kong.client.principal, pull the address out of the Principal’s metadata, and write it to kong.service.target.
Prerequisites
Kong Konnect
This is a Konnect tutorial and requires a Konnect personal access token.
-
Create a new personal access token by opening the Konnect PAT page and selecting Generate Token.
-
Export your token to an environment variable:
export KONNECT_TOKEN='YOUR_KONNECT_PAT' -
Run the quickstart script to automatically provision a Control Plane and Data Plane, and configure your environment:
curl -Ls https://get.konghq.com/quickstart | bash -s -- -k $KONNECT_TOKEN \ --deck-outputThis sets up a Konnect Control Plane named
quickstart, provisions a local Data Plane, and prints out the following environment variable exports:export DECK_KONNECT_TOKEN=$KONNECT_TOKEN export DECK_KONNECT_CONTROL_PLANE_NAME=quickstart export DECK_KONNECT_ADDR=https://us.api.konghq.com export KONNECT_CONTROL_PLANE_URL=https://us.api.konghq.com export KONNECT_PROXY_URL='http://localhost:8000'Copy and paste these into your terminal to configure your session.
decK v1.66.1+
To complete this tutorial, install decK. We recommend keeping decK up to date with the latest version (1.66.1).
decK is a CLI tool for managing Kong Gateway declaratively with state files.
This guide uses deck gateway apply, which directly applies entity configuration to your Gateway instance.
You can check your current decK version with deck version.
Required entities
For this tutorial, you’ll need Kong Gateway entities, like Gateway Services and Routes, pre-configured. These entities are essential for Kong Gateway to function but installing them isn’t the focus of this guide. Follow these steps to pre-configure them:
-
Run the following command:
echo ' _format_version: "3.0" services: - name: example-service url: http://httpbin.konghq.com/anything routes: - name: example-route paths: - "/anything" service: name: example-service protocols: - http - https ' | deck gateway apply -
To learn more about entities, you can read our entities documentation.
Kong Identity directory
A directory is a regional collection of principals. A Konnect organization supports only one Kong Identity directory. Create a directory for the tutorial, or look up your existing one.
Kong’s router runs before authentication, so it can’t directly route traffic based on who is making a request.
This guide solves that using Kong Identity and two plugins working together in the access phase:
- You store each caller’s backend address as Principal metadata in Kong Identity.
- OpenID Connect verifies the bearer token and hydrates the matching Principal, including its metadata.
- Datakit reads the Principal, pulls the address out of its metadata, and sets it as the backend target for that request.
All callers share one Route and one Service, and the Datakit plugin decides dynamically which backend responds after authentication.
Because the routing map lives in Kong Identity, you create a new caller using the Principal API. You don’t need to edit the Datakit configuration or run a decK sync.
This guide routes directly to a host:port backend, and bypasses Upstream entities, load balancing, health checks, and retries.
Use it when each backend is a fixed address and you don’t need a pool.
Note: The OpenID Connect plugin has a higher static priority than Datakit, so it always runs first in the
accessphase. No explicit plugin ordering configuration is required.
Create an authorization server in Kong Identity
An authorization server in Kong Identity issues the OAuth tokens that callers present to authenticate to your service. We recommend that you create different authorization servers for different environments. The authorization server name is unique per each organization and each Konnect region.
Create an authorization server using the /v1/auth-servers endpoint:
_response=$(curl -X POST "https://us.api.konghq.com/v1/auth-servers" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"name": "datakit-routing",
"description": "Authorization server for Datakit host routing",
"audience": "http://myhttpbin.dev"
}')Export the env variables:
export AUTH_SERVER_ID=$(echo "$_response" | jq -r ".id")
export ISSUER_URL=$(echo "$_response" | jq -r ".issuer")Create the clients
Create one client per caller. Each client is the machine-to-machine credential that a caller uses to fetch a token. In this example, Konnect autogenerates the client ID and secret, but you can also specify them yourself instead.
Create the orders-caller client using the /v1/auth-servers/$AUTH_SERVER_ID/clients endpoint:
_response=$(curl -X POST "https://us.api.konghq.com/v1/auth-servers/$AUTH_SERVER_ID/clients" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"name": "orders-caller",
"allow_all_scopes": true,
"allow_scopes": [],
"access_token_duration": 3600,
"grant_types": [
"client_credentials"
],
"response_types": [
"token"
],
"redirect_uris": [],
"login_uri": ""
}')Export the env variables:
export CLIENT_A_ID=$(echo "$_response" | jq -r ".id")
export CLIENT_A_SECRET=$(echo "$_response" | jq -r ".client_secret")Create the reports-caller client:
_response=$(curl -X POST "https://us.api.konghq.com/v1/auth-servers/$AUTH_SERVER_ID/clients" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"name": "reports-caller",
"allow_all_scopes": true,
"allow_scopes": [],
"access_token_duration": 3600,
"grant_types": [
"client_credentials"
],
"response_types": [
"token"
],
"redirect_uris": [],
"login_uri": ""
}')Export the env variables:
export CLIENT_B_ID=$(echo "$_response" | jq -r ".id")
export CLIENT_B_SECRET=$(echo "$_response" | jq -r ".client_secret")Create the legacy-caller client. This caller’s Principal won’t carry any routing metadata, so you can use it to confirm the default backend:
_response=$(curl -X POST "https://us.api.konghq.com/v1/auth-servers/$AUTH_SERVER_ID/clients" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"name": "legacy-caller",
"allow_all_scopes": true,
"allow_scopes": [],
"access_token_duration": 3600,
"grant_types": [
"client_credentials"
],
"response_types": [
"token"
],
"redirect_uris": [],
"login_uri": ""
}')Export the env variables:
export CLIENT_C_ID=$(echo "$_response" | jq -r ".id")
export CLIENT_C_SECRET=$(echo "$_response" | jq -r ".client_secret")Create the Principals
Create one Principal per caller in the directory you created in the prerequisites.
The metadata object is where the routing decision lives: backend_target holds the host:port address, and backend_scheme holds the protocol Kong Gateway uses to reach it.
Note: Metadata is inherited hierarchically. A Principal inherits its directory’s metadata and can override it, and a credential inherits the Principal’s metadata and can override it. You can set a
backend_targeton the directory to give every Principal a shared default.
Create the orders-caller Principal, routed to httpbin.konghq.com over HTTP:
PRINCIPAL_A_ID=$(curl -X POST "https://us.api.konghq.com/v2/directories/$DIRECTORY_ID/principals" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"display_name": "orders-caller",
"description": "Caller routed to the orders backend",
"metadata": {
"backend_target": "httpbin.konghq.com:80",
"backend_scheme": "http"
}
}' | jq -r ".id"
)Create the reports-caller Principal, routed to httpbun.com over HTTPS:
PRINCIPAL_B_ID=$(curl -X POST "https://us.api.konghq.com/v2/directories/$DIRECTORY_ID/principals" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"display_name": "reports-caller",
"description": "Caller routed to the reports backend",
"metadata": {
"backend_target": "httpbun.com:443",
"backend_scheme": "https"
}
}' | jq -r ".id"
)Create the legacy-caller Principal with no routing metadata:
PRINCIPAL_C_ID=$(curl -X POST "https://us.api.konghq.com/v2/directories/$DIRECTORY_ID/principals" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"display_name": "legacy-caller",
"description": "Caller with no routing metadata, falls back to the default backend"
}' | jq -r ".id"
)Link each client to its Principal
Your Kong Identity authorization server issues the tokens. Add an auth_server_client identity to each Principal so Kong Identity can map the tokens it issues back to the right Principal.
Link orders-caller using the /v2/directories/$DIRECTORY_ID/principals/$PRINCIPAL_ID/identities endpoint:
curl -X POST "https://us.api.konghq.com/v2/directories/$DIRECTORY_ID/principals/$PRINCIPAL_A_ID/identities" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"type": "auth_server_client",
"auth_server_id": "'$AUTH_SERVER_ID'",
"client_id": "'$CLIENT_A_ID'"
}'Link reports-caller:
curl -X POST "https://us.api.konghq.com/v2/directories/$DIRECTORY_ID/principals/$PRINCIPAL_B_ID/identities" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"type": "auth_server_client",
"auth_server_id": "'$AUTH_SERVER_ID'",
"client_id": "'$CLIENT_B_ID'"
}'Link legacy-caller:
curl -X POST "https://us.api.konghq.com/v2/directories/$DIRECTORY_ID/principals/$PRINCIPAL_C_ID/identities" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"type": "auth_server_client",
"auth_server_id": "'$AUTH_SERVER_ID'",
"client_id": "'$CLIENT_C_ID'"
}'Get the directory name
To configure the OpenID Connect plugin, you need the name of the directory you created. Store it as DECK_DIRECTORY_NAME so decK can read it during sync:
DECK_DIRECTORY_NAME=$(curl -X GET "https://us.api.konghq.com/v2/directories" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN" | jq -r ".data[0].name"
)Export the directory name so decK can read it during sync:
export DECK_DIRECTORY_NAMEGenerate a salt token
Starting with decK v1.59+, you need to set cache_tokens_salt to avoid regenerating session credentials during sync. Generate a salt token:
export DECK_TOKEN_SALT="$(openssl rand -base64 16)"Enable the OpenID Connect plugin
Export the issuer URL to decK:
export DECK_ISSUER_URL=$ISSUER_URLConfigure the OpenID Connect plugin to verify bearer tokens issued by Kong Identity and hydrate the matching Principal:
echo '
_format_version: "3.0"
plugins:
- name: openid-connect
service: example-service
config:
issuer: "${{ env "DECK_ISSUER_URL" }}"
auth_methods:
- bearer
audience:
- http://myhttpbin.dev
ssl_verify: true
principals:
enabled: true
directory: "${{ env "DECK_DIRECTORY_NAME" }}"
cache_tokens_salt: "${{ env "DECK_TOKEN_SALT" }}"
' | deck gateway apply -In this configuration:
issuer: The Kong Identity authorization server issuer URL. The plugin discovers the JWKS endpoint from this URL and uses it to verify token signatures.auth_methods: [bearer]: The plugin only accepts tokens in theAuthorization: Bearerheader.audience: Must match theaudienceyou set on the authorization server, so the plugin accepts theaudclaim in the token.principals.enabled: true: After verifying the token, the plugin looks up the matching Principal in the named directory and makes it available to later plugins throughkong.client.principal.principals.directory: The name of the Kong Identity directory holding your Principals.
Enable the Datakit plugin
Configure the Datakit plugin to read the Principal that the OpenID Connect plugin hydrated and use its metadata as the backend target:
echo '
_format_version: "3.0"
plugins:
- name: datakit
service: example-service
config:
nodes:
- name: GET_PRINCIPAL
type: property
property: kong.client.principal
- name: PICK_TARGET
type: jq
input: GET_PRINCIPAL
jq: |
{
"target": (.metadata.backend_target // "httpbin.konghq.com:80"),
"scheme": (.metadata.backend_scheme // "http")
}
- name: EXTRACT_TARGET
type: jq
input: PICK_TARGET
jq: ".target"
- name: SET_TARGET
type: property
property: kong.service.target
input: EXTRACT_TARGET
- name: EXTRACT_SCHEME
type: jq
input: PICK_TARGET
jq: ".scheme"
- name: SET_SCHEME
type: property
property: kong.service.request.scheme
input: EXTRACT_SCHEME
debug: true
' | deck gateway apply -In this configuration:
GET_PRINCIPAL: Reads thekong.client.principalobject that the OpenID Connect plugin populates. No input is connected because this is a read-only operation.PICK_TARGET: Readsbackend_targetandbackend_schemeout of the Principal’s metadata and returns both in one object. Returning both values from one node avoids reading the Principal twice. The//operator supplies a default backend for Principals that carry no routing metadata.EXTRACT_TARGET: Extracts the.targetfield from thePICK_TARGEToutput.SET_TARGET: Writes thehost:portstring tokong.service.target, overriding the backend for this request. This bypasses load balancing, health checks, and retries.EXTRACT_SCHEME: Extracts the.schemefield from thePICK_TARGEToutput.SET_SCHEME: Writes the scheme tokong.service.request.scheme. This is required when backends use different protocols, so Kong Gateway uses the correct scheme when connecting.debug: true: Enables trace output for this tutorial. Remove it before using this configuration in production.
Validate the routing
To validate that routing via Datakit is working, fetch an access token for each client from the Kong Identity authorization server, then send it as a bearer token and verify that each caller reaches the correct backend.
In the following requests, you set the X-Datakit-Debug-Trace: true request header so that Datakit returns a JSON trace in the response body showing each node’s input and output.
-
Fetch a token as
orders-caller:export TOKEN_A=$(curl -s -X POST "$ISSUER_URL/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=$CLIENT_A_ID" \ -d "client_secret=$CLIENT_A_SECRET" | jq -r .access_token)Send a request as
orders-caller:curl -si http://localhost:8000/anything \ -H "Authorization: Bearer $TOKEN_A" \ -H "X-Datakit-Debug-Trace: true"The response comes from
httpbin.konghq.com. In the response body, find thecompleteevent for each node and check:GET_PRINCIPAL:value.value.display_nameisorders-caller.PICK_TARGET:value.valueis{"target":"httpbin.konghq.com:80","scheme":"http"}.EXTRACT_TARGET:value.valueishttpbin.konghq.com:80.SET_TARGET:value.valueishttpbin.konghq.com:80.EXTRACT_SCHEME:value.valueishttp.SET_SCHEME:value.valueishttp.
-
Fetch a token as
reports-caller:export TOKEN_B=$(curl -s -X POST "$ISSUER_URL/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=$CLIENT_B_ID" \ -d "client_secret=$CLIENT_B_SECRET" | jq -r .access_token)Send a request as
reports-caller:curl -si http://localhost:8000/anything \ -H "Authorization: Bearer $TOKEN_B" \ -H "X-Datakit-Debug-Trace: true"The response comes from
httpbun.com, confirming the request was routed to a different backend.EXTRACT_TARGETandSET_TARGETshould showhttpbun.com:443, andEXTRACT_SCHEMEandSET_SCHEMEshould showhttps. -
Fetch a token as
legacy-callerto confirm the fallback:export TOKEN_C=$(curl -s -X POST "$ISSUER_URL/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=$CLIENT_C_ID" \ -d "client_secret=$CLIENT_C_SECRET" | jq -r .access_token)Send a request as
legacy-caller:curl -si http://localhost:8000/anything \ -H "Authorization: Bearer $TOKEN_C" \ -H "X-Datakit-Debug-Trace: true"GET_PRINCIPALshould showvalue.value.display_nameaslegacy-callerwith an emptymetadataobject, andEXTRACT_TARGETandSET_TARGETshould resolve tohttpbin.konghq.com:80because the Principal carries nobackend_target. -
Send a request with no token. The OpenID Connect plugin rejects it before Datakit runs:
curl -i $KONNECT_PROXY_URL/anything This request returns a 401 error with the message Unauthorized.
Change a caller’s backend without a decK sync
Because the routing map lives in Kong Identity, you can move a caller to a different backend by updating its Principal metadata.
Move orders-caller to httpbun.com:
curl -X PATCH "https://us.api.konghq.com/v2/directories/$DIRECTORY_ID/principals/$PRINCIPAL_A_ID" \
--no-progress-meter --fail-with-body \
-H "Authorization: Bearer $KONNECT_TOKEN"\
-H "Content-Type: application/json" \
--json '{
"metadata": {
"backend_target": "httpbun.com:443",
"backend_scheme": "https"
}
}'Send a request as orders-caller again. Once the OpenID Connect plugin’s token cache expires, the response comes from httpbun.com with no change to your gateway configuration:
curl -si http://localhost:8000/anything \
-H "Authorization: Bearer $TOKEN_A" \
-H "X-Datakit-Debug-Trace: true"Cleanup
Clean up Konnect environment
If you created a new control plane and want to conserve your free trial credits or avoid unnecessary charges, delete the new control plane used in this tutorial.
Destroy the Kong Gateway container
curl -Ls https://get.konghq.com/quickstart | bash -s -- -dFAQs
Can I retrieve my client’s secret again?
No, the secret is only shared once when the client is created. Store it securely.
What happens if a token can’t be matched to a Principal?
By default, the OpenID Connect plugin returns a 401 when it verifies a token but can’t match it to a Principal in the directory.
Set principals.error_on_miss to false if you want the request to continue without an authenticated Principal.
If you do that, handle a null kong.client.principal in your Datakit configuration.