diff --git a/app/_event_gateway_policies/modify-headers/index.md b/app/_event_gateway_policies/modify-headers/index.md
index c677e91688..e49346dfe9 100644
--- a/app/_event_gateway_policies/modify-headers/index.md
+++ b/app/_event_gateway_policies/modify-headers/index.md
@@ -55,6 +55,10 @@ rows:
description: If a record fits a specific condition, add a custom header of your choice.
- use_case: "[Tutorial: Filter Kafka records by classification headers](/event-gateway/filter-records-by-classification/)"
description: Use a [Schema Validation policy](/event-gateway/policies/schema-validation-produce/) to parse JSON records, and use a nested Modify Headers policy to add a header to specific records.
+ - use_case: "[Tutorial: Enrich Kafka SASL PLAIN connections with Kong Identity principal metadata](/event-gateway/kong-identity-metadata-integration/)"
+ description: Look up the SASL-authenticated principal in a Kong Identity directory, and add a header to records based on the principal's metadata.
+ - use_case: "[Tutorial: Enrich Kafka OAuth connections with Kong Identity principal metadata](/event-gateway/kong-identity-jwt-metadata-integration/)"
+ description: Look up the JWT-authenticated principal in a Kong Identity directory by `iss` and `sub`, and add a header to records based on the principal's metadata.
{% endtable %}
diff --git a/app/_how-tos/event-gateway/kong-identity-jwt-metadata-integration.md b/app/_how-tos/event-gateway/kong-identity-jwt-metadata-integration.md
new file mode 100644
index 0000000000..2b0254d50d
--- /dev/null
+++ b/app/_how-tos/event-gateway/kong-identity-jwt-metadata-integration.md
@@ -0,0 +1,450 @@
+---
+title: Enrich Kafka OAuth connections with Kong Identity principal metadata
+content_type: how_to
+breadcrumbs:
+ - /event-gateway/
+
+permalink: /event-gateway/kong-identity-jwt-metadata-integration/
+
+products:
+ - event-gateway
+
+works_on:
+ - konnect
+
+tags:
+ - event-gateway
+ - kafka
+
+description: "Look up Kong Identity principal metadata from a JWT-authenticated Kafka connection and use it to drive {{site.event_gateway}} policies."
+
+tldr:
+ q: How do I use Kong Identity principal metadata in {{site.event_gateway_short}} policies for JWT-authenticated clients?
+ a: |
+ 1. Create a Kong Identity auth server, scope, and client.
+ 1. Create a Kong Identity directory, principal with metadata, and an `oidc` identity that matches the JWT's `iss` and `sub`.
+ 1. Configure a virtual cluster with `oauth_bearer` authentication and `fetch_kong_identity_principal` pointing at the directory.
+ 1. Create a Modify Headers policy with a condition on `context.auth.principal.metadata`.
+ 1. Produce and consume a record to see the policy fire.
+
+tools:
+ - konnect-api
+
+prereqs:
+ inline:
+ - title: Install kafkactl
+ position: before
+ content: |
+ Install [kafkactl](https://github.com/deviceinsight/kafkactl?tab=readme-ov-file#installation). You'll need it to interact with Kafka clusters.
+ Version >= 5.17.0 is needed to support script driven OAuth token generation.
+
+ - title: Start a local Kafka cluster
+ position: before
+ include_content: knep/docker-compose-start
+ - title: Kong Identity directory
+ include_content: prereqs/kong-identity-directory
+ icon_url: /assets/icons/identity.svg
+
+cleanup:
+ inline:
+ - title: Clean up {{site.event_gateway}} resources
+ include_content: cleanup/products/event-gateway
+ icon_url: /assets/icons/gateway.svg
+
+related_resources:
+ - text: Set up {{site.event_gateway}} with Kong Identity OAuth
+ url: /event-gateway/kong-identity-oauth/
+ - text: Enrich Kafka SASL PLAIN connections with Kong Identity principal metadata
+ url: /event-gateway/kong-identity-metadata-integration/
+ - text: Modify Headers policy
+ url: /event-gateway/policies/modify-headers/
+ - text: "{{site.event_gateway_short}} expressions language"
+ url: /event-gateway/expressions/
+
+min_version:
+ event-gateway: '1.2.0'
+
+automated_tests: false
+---
+
+In this guide, you'll authenticate a Kafka client to {{site.event_gateway_short}} with a JWT issued by a Kong Identity auth server, look up the connecting principal in a Kong Identity directory by the token's issuer and subject, and use the principal's metadata to drive a Modify Headers policy.
+
+For `oauth_bearer` authentication, {{site.event_gateway_short}} always looks up the Kong Identity identity by matching the JWT's `iss` and `sub` claims against an `oidc` identity in the directory. No extra lookup-key configuration is needed.
+
+
+{% mermaid %}
+flowchart LR
+ C[Kafka client]
+ subgraph EG [" {{site.event_gateway_short}} "]
+ VC[oauth_bearer
virtual cluster]
+ end
+ KI[(Kong Identity
directory)]
+ subgraph K [Kafka cluster]
+ L["PLAINTEXT :9092"]
+ end
+ C -->|SASL/OAUTHBEARER
JWT| VC
+ VC -.->|lookup by iss + sub| KI
+ KI -.->|principal metadata
team=operators| VC
+ VC -->|forward request| L
+ VC -->|record with
x-team header| C
+{% endmermaid %}
+
+
+## Create an auth server in Kong Identity
+
+Create an auth server using the [`/v1/auth-servers` endpoint](/api/konnect/kong-identity/v1/#/operations/createAuthServer):
+
+
+{% konnect_api_request %}
+url: /v1/auth-servers
+status_code: 201
+method: POST
+headers:
+ - 'Content-Type: application/json'
+body:
+ name: "Event Gateway Auth"
+ audience: "http://event-gateway"
+ description: "Auth server for Event Gateway"
+extract_body:
+ - name: 'id'
+ variable: AUTH_SERVER_ID
+ - name: 'issuer'
+ variable: ISSUER_URL
+capture:
+ - variable: AUTH_SERVER_ID
+ jq: '.id'
+ - variable: ISSUER_URL
+ jq: '.issuer'
+{% endkonnect_api_request %}
+
+
+## Configure the auth server with scopes
+
+Configure a scope using the [`/v1/auth-servers/$AUTH_SERVER_ID/scopes` endpoint](/api/konnect/kong-identity/v1/#/operations/createAuthServerScope):
+
+
+{% konnect_api_request %}
+url: /v1/auth-servers/$AUTH_SERVER_ID/scopes
+status_code: 201
+method: POST
+headers:
+ - 'Content-Type: application/json'
+body:
+ name: "kafka"
+ description: "Scope for Kafka access"
+ default: false
+ include_in_metadata: false
+ enabled: true
+extract_body:
+ - name: 'id'
+ variable: SCOPE_ID
+capture:
+ - variable: SCOPE_ID
+ jq: ".id"
+{% endkonnect_api_request %}
+
+
+## Create a client in the auth server
+
+The client is the machine-to-machine credential. {{site.konnect_short_name}} autogenerates the client ID and secret. Configure the client using the [`/v1/auth-servers/$AUTH_SERVER_ID/clients` endpoint](/api/konnect/kong-identity/v1/#/operations/createAuthServerClient):
+
+
+{% konnect_api_request %}
+url: /v1/auth-servers/$AUTH_SERVER_ID/clients
+status_code: 201
+method: POST
+headers:
+ - 'Content-Type: application/json'
+body:
+ name: kafka-client
+ grant_types:
+ - client_credentials
+ allow_all_scopes: false
+ allow_scopes:
+ - $SCOPE_ID
+ access_token_duration: 3600
+ id_token_duration: 3600
+ response_types:
+ - id_token
+ - token
+extract_body:
+ - name: 'client_secret'
+ variable: CLIENT_SECRET
+ - name: 'id'
+ variable: CLIENT_ID
+capture:
+ - variable: CLIENT_SECRET
+ jq: '.client_secret'
+ - variable: CLIENT_ID
+ jq: '.id'
+{% endkonnect_api_request %}
+
+
+## Create a principal with team metadata
+
+Create a principal in the directory you created in the prerequisites and attach the `team` metadata. The Modify Headers policy will read this value at request time:
+
+
+{% konnect_api_request %}
+url: /v2/directories/$DIRECTORY_ID/principals
+status_code: 201
+method: POST
+body:
+ display_name: john
+ description: Principal for the kafka-client OAuth client
+ metadata:
+ team: operators
+extract_body:
+ - name: id
+ variable: PRINCIPAL_ID
+capture:
+ - variable: PRINCIPAL_ID
+ jq: ".id"
+{% endkonnect_api_request %}
+
+
+## Create an OIDC identity for the JWT subject
+
+Create an `oidc` identity that links the principal to the JWT issued by the auth server. {{site.event_gateway_short}} will match the JWT's `iss` against `issuer` and the JWT's `sub` against the configured claim:
+
+
+{% konnect_api_request %}
+url: /v2/directories/$DIRECTORY_ID/principals/$PRINCIPAL_ID/identities
+status_code: 201
+method: POST
+body:
+ type: oidc
+ issuer: $ISSUER_URL
+ claim:
+ name: sub
+ value: $CLIENT_ID
+{% endkonnect_api_request %}
+
+
+For the `client_credentials` grant, Kong Identity sets the JWT `sub` claim to the client ID, so the identity's `claim.value` is the `CLIENT_ID` captured earlier.
+
+## Connect the `event-gateway-quickstart` to the same network as Kafka
+
+Configure the `event-gateway-quickstart` container you created in the prerequisites to use the same network as your Kafka cluster:
+
+```sh
+docker network connect kafka_event_gateway event-gateway-quickstart
+```
+
+This allows the two containers to communicate.
+
+## Create the backend cluster
+
+
+{% include knep/create-backend-cluster.md insecure=true %}
+
+
+## Create a virtual cluster
+
+Create a [virtual cluster](/event-gateway/entities/virtual-cluster/) that terminates `oauth_bearer` authentication and fetches the principal from the Kong Identity directory:
+
+
+{% konnect_api_request %}
+url: /v1/event-gateways/$EVENT_GATEWAY_ID/virtual-clusters
+status_code: 201
+method: POST
+body:
+ name: identity_vc
+ destination:
+ id: $BACKEND_CLUSTER_ID
+ dns_label: identity-vc
+ acl_mode: passthrough
+ authentication:
+ - type: oauth_bearer
+ mediation: terminate
+ jwks:
+ endpoint: $ISSUER_URL/.well-known/jwks
+ fetch_kong_identity_principal:
+ directory: kong-identity-directory
+ failure_mode: error
+extract_body:
+ - name: id
+ variable: VIRTUAL_CLUSTER_ID
+capture:
+ - variable: VIRTUAL_CLUSTER_ID
+ jq: ".id"
+{% endkonnect_api_request %}
+
+
+For `oauth_bearer` authentication, the `fetch_kong_identity_principal` block doesn't need a `fetch_by` field: the principal is always looked up by the JWT's `iss` and `sub` claims.
+
+## Create a listener
+
+Run the following command to create a new [listener](/event-gateway/entities/listener/):
+
+
+{% konnect_api_request %}
+url: /v1/event-gateways/$EVENT_GATEWAY_ID/listeners
+status_code: 201
+method: POST
+body:
+ name: identity_listener
+ addresses:
+ - 0.0.0.0
+ ports:
+ - 19092-19095
+extract_body:
+ - name: id
+ variable: LISTENER_ID
+capture:
+ - variable: LISTENER_ID
+ jq: ".id"
+{% endkonnect_api_request %}
+
+
+## Create a listener policy
+
+Add a [Forward to Virtual Cluster](/event-gateway/policies/forward-to-virtual-cluster/) policy that routes the listener to the virtual cluster:
+
+
+{% konnect_api_request %}
+url: /v1/event-gateways/$EVENT_GATEWAY_ID/listeners/$LISTENER_ID/policies
+status_code: 201
+method: POST
+body:
+ type: forward_to_virtual_cluster
+ name: forward_to_identity_vc
+ config:
+ type: port_mapping
+ advertised_host: localhost
+ destination:
+ id: $VIRTUAL_CLUSTER_ID
+{% endkonnect_api_request %}
+
+
+## Create the Modify Headers policy
+
+Add a [Modify Headers](/event-gateway/policies/modify-headers/) policy that sets the `x-team` header on consumed records only when the principal's `team` metadata equals `operators`:
+
+
+{% konnect_api_request %}
+url: /v1/event-gateways/$EVENT_GATEWAY_ID/virtual-clusters/$VIRTUAL_CLUSTER_ID/consume-policies
+status_code: 201
+method: POST
+body:
+ type: modify_headers
+ name: tag-operators-team
+ condition: context.auth.principal.metadata.team == "operators"
+ config:
+ actions:
+ - op: set
+ key: x-team
+ value: operators
+{% endkonnect_api_request %}
+
+
+## Configure kafkactl
+
+{:.danger}
+> This step requires a `kafkactl` version 5.17.0 or later. To check your version, run `kafkactl version`.
+>
+> Note that this script is for demo purposes only and hard-codes the client ID, client secret, and scope.
+> For production, we recommend securing sensitive data.
+
+`kafkactl` generates tokens using a script. Create the script:
+
+
+{% validation custom-command %}
+command: |
+ cat < get-oauth-token.sh
+ #!/bin/bash
+ curl -s --fail -X POST "$ISSUER_URL/oauth/token" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "grant_type=client_credentials" \
+ -d "client_id=$CLIENT_ID" \
+ -d "client_secret=$CLIENT_SECRET" \
+ -d "scope=kafka" | jq -r '{"token": .access_token}'
+ EOF
+ chmod u+x get-oauth-token.sh
+expected:
+ return_code: 0
+render_output: false
+{% endvalidation %}
+
+
+Create the `kafkactl.yaml` configuration:
+
+
+{% validation custom-command %}
+command: |
+ cat < kafkactl.yaml
+ contexts:
+ direct:
+ brokers:
+ - localhost:9094
+ - localhost:9095
+ - localhost:9096
+ vc:
+ sasl:
+ enabled: true
+ mechanism: oauth
+ tokenprovider:
+ plugin: generic
+ options:
+ script: ./get-oauth-token.sh
+ args: []
+ brokers:
+ - localhost:19092
+ EOF
+expected:
+ return_code: 0
+render_output: false
+{% endvalidation %}
+
+
+## Create a topic
+
+Create the `orders` topic using the `direct` context:
+
+
+{% validation custom-command %}
+command: |
+ kafkactl -C kafkactl.yaml --context direct create topic orders
+expected:
+ return_code: 0
+ message: "topic created: orders"
+render_output: false
+{% endvalidation %}
+
+
+## Validate
+
+Produce a record through the virtual cluster:
+
+
+{% validation custom-command %}
+command: |
+ kafkactl -C kafkactl.yaml --context vc produce orders --value="test-message"
+expected:
+ return_code: 0
+ message: "message produced (partition=0 offset=0)"
+render_output: false
+{% endvalidation %}
+
+
+Consume the record back through the virtual cluster with `--print-headers` so you can see the header added by the Modify Headers policy:
+
+
+{% validation custom-command %}
+command: |
+ kafkactl -C kafkactl.yaml --context vc consume orders --print-headers --from-beginning --exit
+expected:
+ return_code: 0
+ message: "x-team:operators#test-message"
+render_output: false
+{% endvalidation %}
+
+
+The output should contain the `x-team` header:
+
+```shell
+x-team:operators#test-message
+```
+{:.no-copy-code}
+
+{{site.event_gateway_short}} validated the JWT against the auth server's JWKS, looked up the principal in the `kong-identity-directory` Kong Identity directory by matching the token's `iss` and `sub` against the `oidc` identity, attached the principal's metadata to the connection, and applied the Modify Headers policy because `context.auth.principal.metadata.team` was `operators`.
diff --git a/app/_how-tos/event-gateway/kong-identity-metadata-integration.md b/app/_how-tos/event-gateway/kong-identity-metadata-integration.md
new file mode 100644
index 0000000000..946ce665bf
--- /dev/null
+++ b/app/_how-tos/event-gateway/kong-identity-metadata-integration.md
@@ -0,0 +1,382 @@
+---
+title: Enrich Kafka SASL PLAIN connections with Kong Identity principal metadata
+content_type: how_to
+breadcrumbs:
+ - /event-gateway/
+
+permalink: /event-gateway/kong-identity-metadata-integration/
+
+products:
+ - event-gateway
+ - identity
+
+works_on:
+ - konnect
+
+tags:
+ - event-gateway
+ - kafka
+
+description: "Look up Kong Identity principal metadata from a SASL-authenticated Kafka connection and use it to drive {{site.event_gateway}} policies."
+
+tldr:
+ q: How do I use Kong Identity principal metadata in {{site.event_gateway_short}} policies?
+ a: |
+ 1. Create a Kong Identity directory, principal with metadata, and a `custom` identity keyed by the SASL username.
+ 1. Configure a virtual cluster with `sasl_plain` `passthrough` authentication and `fetch_kong_identity_principal` pointing at the directory.
+ 1. Create a Modify Headers policy with a condition on `context.auth.principal.metadata`.
+ 1. Produce and consume a record to see the policy fire.
+
+tools:
+ - konnect-api
+
+prereqs:
+ skip_product: true
+ inline:
+ - title: Install kafkactl
+ position: before
+ include_content: knep/kafkactl
+ - title: Kong Identity directory
+ include_content: prereqs/kong-identity-directory
+ icon_url: /assets/icons/kong-identity.svg
+
+cleanup:
+ inline:
+ - title: Clean up {{site.event_gateway}} resources
+ include_content: cleanup/products/event-gateway
+ icon_url: /assets/icons/gateway.svg
+
+related_resources:
+ - text: Authenticate {{site.event_gateway}} connections to Kafka using SASL/PLAIN
+ url: /event-gateway/configure-sasl-plain-backend-cluster-auth/
+ - text: Modify Headers policy
+ url: /event-gateway/policies/modify-headers/
+ - text: "{{site.event_gateway_short}} expressions language"
+ url: /event-gateway/expressions/
+ - text: Backend clusters
+ url: /event-gateway/entities/backend-cluster/
+
+min_version:
+ event-gateway: '1.2.0'
+
+automated_tests: false
+---
+
+In this guide, you'll authenticate a Kafka client to a SASL-secured broker through {{site.event_gateway_short}}, look up the connecting principal in a Kong Identity directory by its SASL username, and use the principal's metadata to drive a [Modify Headers policy](/event-gateway/policies/modify-headers/).
+
+{% mermaid %}
+flowchart LR
+ C[Kafka client]
+ subgraph EG [" {{site.event_gateway_short}} "]
+ VC[sasl_plain passthrough
virtual cluster]
+ end
+ KI[(Kong Identity
directory)]
+ subgraph K [Kafka cluster]
+ L["SASL_PLAINTEXT :9082"]
+ end
+ C -->|SASL/PLAIN
user=john| VC
+ VC -.->|lookup by sasl_username| KI
+ KI -.->|principal metadata
team=operators| VC
+ VC -->|SASL/PLAIN passthrough| L
+ VC -->|record with
x-team header| C
+{% endmermaid %}
+
+## Start the secured Kafka cluster
+
+Create the JAAS configuration file that defines the SASL/PLAIN credentials:
+
+```bash
+cat <<'EOF' > kafka_server_jaas.conf
+KafkaServer {
+ org.apache.kafka.common.security.plain.PlainLoginModule required
+ username="eventgateway"
+ password="eventgateway-secret"
+ user_eventgateway="eventgateway-secret"
+ user_john="john-secret";
+};
+EOF
+```
+
+The broker accepts two SASL/PLAIN users: `eventgateway` (used by {{site.event_gateway_short}} itself for broker discovery) and `john` (used by the Kafka client and matched against Kong Identity).
+
+Create the Docker Compose file:
+
+```bash
+cat <<'EOF' > docker-compose.yaml
+{% include_cached _files/event-gateway/docker-compose-sasl.yaml %}
+EOF
+```
+
+The broker exposes a `SASL_PLAINTEXT` listener on port `9082` in the Docker network for {{site.event_gateway_short}} connections, and a `PLAINTEXT` listener on ports `9094`/`9095`/`9096` for direct local access.
+
+Start the cluster:
+
+```bash
+docker compose up -d
+```
+
+## Create an {{site.event_gateway_short}} control plane and data plane
+
+Run the [quickstart script](https://get.konghq.com/event-gateway) to provision a local data plane and configure your environment:
+
+```bash
+curl -Ls https://get.konghq.com/event-gateway | bash -s -- -k $KONNECT_TOKEN -N kafka_event_gateway
+```
+
+Copy the exported variable into your terminal:
+
+```bash
+export EVENT_GATEWAY_ID=your-gateway-id
+```
+
+{% include_cached /knep/quickstart-note.md %}
+
+## Create a principal with team metadata
+
+Create a principal in the directory and attach the `team` metadata. The Modify Headers policy will read this value at request time:
+
+
+{% konnect_api_request %}
+url: /v2/directories/$DIRECTORY_ID/principals
+status_code: 201
+method: POST
+body:
+ display_name: john
+ description: Principal that maps to the john SASL user
+ metadata:
+ team: operators
+extract_body:
+ - name: id
+ variable: PRINCIPAL_ID
+capture:
+ - variable: PRINCIPAL_ID
+ jq: ".id"
+{% endkonnect_api_request %}
+
+
+## Create a custom identity for the SASL username
+
+Create a `custom` identity that links the principal to the SASL username sent by the Kafka client. {{site.event_gateway_short}} will match the connecting username against the `sasl_username` key:
+
+
+{% konnect_api_request %}
+url: /v2/directories/$DIRECTORY_ID/principals/$PRINCIPAL_ID/identities
+status_code: 201
+method: POST
+body:
+ type: custom
+ key: sasl_username
+ value: john
+{% endkonnect_api_request %}
+
+
+## Create the backend cluster
+
+Create a [backend cluster](/event-gateway/entities/backend-cluster/) configured with the `eventgateway` SASL/PLAIN user. {{site.event_gateway_short}} uses these credentials for its own connection to the broker. Client connections pass through this configuration unchanged because of the virtual cluster's `passthrough` mediation, which you'll configure in the next step:
+
+
+{% konnect_api_request %}
+url: /v1/event-gateways/$EVENT_GATEWAY_ID/backend-clusters
+status_code: 201
+method: POST
+body:
+ name: backend_cluster
+ bootstrap_servers:
+ - kafka1:9082
+ - kafka2:9082
+ - kafka3:9082
+ authentication:
+ type: sasl_plain
+ username: eventgateway
+ password: eventgateway-secret
+ tls:
+ enabled: false
+extract_body:
+ - name: id
+ variable: BACKEND_CLUSTER_ID
+capture:
+ - variable: BACKEND_CLUSTER_ID
+ jq: ".id"
+{% endkonnect_api_request %}
+
+
+## Create a virtual cluster
+
+Create a [virtual cluster](/event-gateway/entities/virtual-cluster/) that accepts SASL/PLAIN connections, forwards them unchanged to the broker, and asks {{site.event_gateway_short}} to fetch the principal from the Kong Identity directory by matching the SASL username against the `sasl_username` key:
+
+
+{% konnect_api_request %}
+url: /v1/event-gateways/$EVENT_GATEWAY_ID/virtual-clusters
+status_code: 201
+method: POST
+body:
+ name: identity_vc
+ destination:
+ id: $BACKEND_CLUSTER_ID
+ dns_label: identity-vc
+ acl_mode: passthrough
+ authentication:
+ - type: sasl_plain
+ mediation: passthrough
+ fetch_kong_identity_principal:
+ directory: kong-identity-directory
+ fetch_by:
+ key: sasl_username
+ failure_mode: error
+extract_body:
+ - name: id
+ variable: VIRTUAL_CLUSTER_ID
+capture:
+ - variable: VIRTUAL_CLUSTER_ID
+ jq: ".id"
+{% endkonnect_api_request %}
+
+
+The `fetch_kong_identity_principal` block tells {{site.event_gateway_short}} to use the SASL username (in this case, `john`) as the lookup value against identities of key `sasl_username` in the directory. When a match is found, the parent principal's metadata is attached to `context.auth.principal.metadata` for the lifetime of the connection.
+
+## Create a listener
+
+Run the following command to create a new [listener](/event-gateway/entities/listener/):
+
+
+{% konnect_api_request %}
+url: /v1/event-gateways/$EVENT_GATEWAY_ID/listeners
+status_code: 201
+method: POST
+body:
+ name: identity_listener
+ addresses:
+ - 0.0.0.0
+ ports:
+ - 19092-19095
+extract_body:
+ - name: id
+ variable: LISTENER_ID
+capture:
+ - variable: LISTENER_ID
+ jq: ".id"
+{% endkonnect_api_request %}
+
+
+## Create a listener policy
+
+Add a [Forward to Virtual Cluster](/event-gateway/policies/forward-to-virtual-cluster/) policy that routes the listener to the virtual cluster:
+
+
+{% konnect_api_request %}
+url: /v1/event-gateways/$EVENT_GATEWAY_ID/listeners/$LISTENER_ID/policies
+status_code: 201
+method: POST
+body:
+ type: forward_to_virtual_cluster
+ name: forward_to_identity_vc
+ config:
+ type: port_mapping
+ advertised_host: localhost
+ destination:
+ id: $VIRTUAL_CLUSTER_ID
+{% endkonnect_api_request %}
+
+
+## Create the Modify Headers policy
+
+Add a [Modify Headers](/event-gateway/policies/modify-headers/) policy that sets the `x-team` header on consumed records only when the principal's `team` metadata equals `operators`:
+
+
+{% konnect_api_request %}
+url: /v1/event-gateways/$EVENT_GATEWAY_ID/virtual-clusters/$VIRTUAL_CLUSTER_ID/consume-policies
+status_code: 201
+method: POST
+body:
+ type: modify_headers
+ name: tag-operators-team
+ condition: context.auth.principal.metadata.team == "operators"
+ config:
+ actions:
+ - op: set
+ key: x-team
+ value: operators
+{% endkonnect_api_request %}
+
+
+## Configure kafkactl
+
+Create a `kafkactl.yaml` config file with a `direct` context that talks to the broker's PLAINTEXT listener, and a `vc` context that connects to the virtual cluster using SASL/PLAIN:
+
+
+{% validation custom-command %}
+command: |
+ cat < kafkactl.yaml
+ contexts:
+ direct:
+ brokers:
+ - localhost:9094
+ - localhost:9095
+ - localhost:9096
+ vc:
+ brokers:
+ - localhost:19092
+ sasl:
+ enabled: true
+ username: john
+ password: john-secret
+ EOF
+expected:
+ return_code: 0
+render_output: false
+{% endvalidation %}
+
+
+## Create a topic
+
+Create the `orders` topic using the `direct` context:
+
+
+{% validation custom-command %}
+command: |
+ kafkactl -C kafkactl.yaml --context direct create topic orders
+expected:
+ return_code: 0
+ message: "topic created: orders"
+render_output: false
+{% endvalidation %}
+
+
+## Validate
+
+Produce a record through the virtual cluster:
+
+
+{% validation custom-command %}
+command: |
+ kafkactl -C kafkactl.yaml --context vc produce orders --value="test-message"
+expected:
+ return_code: 0
+ message: "message produced (partition=0 offset=0)"
+render_output: false
+{% endvalidation %}
+
+
+Consume the record back through the virtual cluster with `--print-headers` so you can see the header added by the Modify Headers policy:
+
+
+{% validation custom-command %}
+command: |
+ kafkactl -C kafkactl.yaml --context vc consume orders --print-headers --from-beginning --exit
+expected:
+ return_code: 0
+ message: "x-team:operators#test-message"
+render_output: false
+{% endvalidation %}
+
+
+The output should contain the `x-team` header:
+
+```shell
+x-team:operators#test-message
+```
+{:.no-copy-code}
+
+{{site.event_gateway_short}} authenticated the client with the broker by passing the SASL/PLAIN credentials straight through, looked up the `john` SASL username in the Kong Identity directory, attached the principal's metadata to the connection, and applied the Modify Headers policy because `context.auth.principal.metadata.team` was `operators`.
+
+The same principal lookup strategy can be used with all other authentication methods (SASL/SCRAM, SASL/OAUTHBEARER, client certificates).
diff --git a/app/_includes/dev-portal/kaa-vs-ace.md b/app/_includes/dev-portal/kaa-vs-ace.md
index 4a40857ff0..d4d0abb864 100644
--- a/app/_includes/dev-portal/kaa-vs-ace.md
+++ b/app/_includes/dev-portal/kaa-vs-ace.md
@@ -5,6 +5,10 @@ When you link an API to a Gateway, you have two options:
These plugins are responsible for applying authentication and authorization on the Gateway Service or control plane.
The [authentication strategy](/dev-portal/auth-strategies/) that you select for the API defines how clients authenticate.
+In {{site.base_gateway}} 3.15 or later, both plugins also look up {{site.identity}} principals to resolve any [plugins applied to a {{site.dev_portal}} application](/dev-portal/self-service/#map-an-application-to-a-consumer), so that Consumer or principal-scoped plugins apply to the application's traffic.
+Principal lookups are cached, so additional lookups aren't required until the cache is evicted.
+Cache eviction is controlled at the principal and {{site.base_gateway}}-level.
+
The following table can help you decide which option to pick:
{% table %}
diff --git a/app/_includes/plugins/ace/ace-overview.md b/app/_includes/plugins/ace/ace-overview.md
index 86dc40d945..2ac2910aa0 100644
--- a/app/_includes/plugins/ace/ace-overview.md
+++ b/app/_includes/plugins/ace/ace-overview.md
@@ -1,9 +1,13 @@
-The Access Control Enforcement (ACE) plugin manages developer access control to APIs published with Dev Portal.
+The Access Control Enforcement (ACE) plugin manages developer access control to APIs published with {{site.dev_portal}}.
You can use the ACE plugin as an alternative to the {{site.konnect_short_name}} application auth (KAA) plugin to link APIs to a Gateway instead of linking APIs to a Gateway Service.
Unlike the KAA plugin, the ACE plugin can link to control planes to configure access control and create API package operations for Gateway Services.
API packages use the ACE plugin to manage developer access control to APIs.
+If you [apply a plugin to a {{site.dev_portal}} application](/dev-portal/self-service/#apply-plugins-to-applications), the ACE plugin looks up the application's principal to resolve the mapped Consumer at runtime, so any Consumer or principal-scoped plugins apply to the application's traffic.
+Principal lookups are cached, so additional lookups aren't required until the cache is evicted.
+Cache eviction is controlled at the principal and {{site.base_gateway}}-level.
+
The ACE plugin runs *after* all other [authentication plugins](/plugins/?category=authentication) run.
For example, if you have [Key Authentication](/plugins/key-auth/) configured and it rejects a request, the ACE plugin *will not* run.
diff --git a/app/_indices/event-gateway.yaml b/app/_indices/event-gateway.yaml
index ad83777204..abef879ecf 100644
--- a/app/_indices/event-gateway.yaml
+++ b/app/_indices/event-gateway.yaml
@@ -44,6 +44,8 @@ sections:
- path: /event-gateway/configure-sasl-plain-backend-cluster-auth/
- path: /event-gateway/configure-mtls-backend-cluster-auth/
- path: /event-gateway/validate-avro-messages-with-schema-registry/
+ - path: /event-gateway/kong-identity-metadata-integration/
+ - path: /event-gateway/kong-identity-jwt-metadata-integration/
- title: "References"
items:
- title: Event Gateway OpenAPI specification
diff --git a/app/_landing_pages/dev-portal/self-service.yaml b/app/_landing_pages/dev-portal/self-service.yaml
deleted file mode 100644
index 84cf98a90e..0000000000
--- a/app/_landing_pages/dev-portal/self-service.yaml
+++ /dev/null
@@ -1,278 +0,0 @@
-metadata:
- title: "Developer self-service"
- content_type: landing_page
- description: "Enable self-service registration flows for developers and applications using authentication strategies and {{site.konnect_short_name}} application auth."
- products:
- - dev-portal
- tags:
- - application-registration
- - authentication
- breadcrumbs:
- - /dev-portal/
-
-rows:
-
- - header:
- type: h1
- text: "Developer self-service and application registration"
- sub_text: Enable self-service registration flows for developers and applications using authentication strategies and {{site.konnect_short_name}} application auth.
-
- - columns:
- - blocks:
- - type: structured_text
- config:
- blocks:
- - type: text
- text: |
- {{site.konnect_short_name}} Dev Portal provides flexible options for controlling access to content and APIs.
- When combined with a [Gateway Service](/gateway/entities/service/), developers visiting a Dev Portal can sign up, create an application, register it with an API, and retrieve API keys without intervention from Dev Portal administrators.
-
- Developer self-service consists of two main components:
- * **User authentication:** Allows users to access your Dev Portal by logging in. You can further customize what logged in users can see using RBAC.
- * **Application registration:** Allows developers to use your APIs using credentials and create applications for them.
-
- - header:
- type: h2
- text: "Enable developer self-service"
- columns:
- - blocks:
- - type: structured_text
- config:
- blocks:
- - type: text
- text: |
- To enable developer self-service, do the following:
- 1. Enable user authentication by navigating to **Settings > Security** in your Dev Portal.
-
- Developer sign ups and application creation require admin approval by default, which can also be configured in the Dev Portal security settings.
-
- For private Dev Portals, user authentication is enabled by default, and the default application auth strategy is key authentication.
- 1. Configure an [application authentication strategy](/dev-portal/auth-strategies/) by navigating to **Settings > Security**.
- 1. Optional: Enable [application sharing](#share-applications-with-a-team) for developer teams by navigating to your Dev Portal in {{site.konnect_short_name}} and going to **Access and approvals > Teams**. Click the team, go to **Settings** and enable **Allow team to own applications**.
- 1. Link an [API to a Gateway Service](/catalog/apis/#gateway-service-link).
-
- This is required to enforce auth strategies.
- 1. Publish an [API to a Dev Portal](/catalog/apis/#publish-your-api-to-dev-portal).
- 1. Select an authentication strategy when publishing the API to a Dev Portal.
- 1. For public content with restricted access, use [visibility settings](/dev-portal/pages-and-content/#page-visibility-and-publishing) to show public pages or APIs to anonymous users while restricting actions to logged-in users.
- - header:
- type: h2
- text: "User authentication"
- columns:
- - blocks:
- - type: structured_text
- config:
- blocks:
- - type: text
- text: |
- Enabling user authentication requires users to register with the Dev Portal.
- You can decide which pages remain public and which ones require authentication.
-
- Dev Portal supports the following user authentication types:
- * Basic authentication
- * OIDC
- * SAML
-
- Additionally, you can enable [RBAC](/dev-portal/developer-rbac/) from your Dev Portal's security settings to control who can view or view and consume APIs in your Dev Portal.
- When RBAC is enabled, any Dev Portal teams and roles you apply to a developer will control their access.
- - header:
- type: h3
- text: "Get started with user authentication"
- columns:
- - blocks:
- - type: card
- config:
- title: Configure Dev Portal SSO
- description: |
- Set up SSO for the {{site.konnect_short_name}} Dev Portal using OpenID Connect (OIDC) or SAML.
- icon: /assets/icons/lock.svg
- cta:
- url: /dev-portal/sso/
- - blocks:
- - type: card
- config:
- title: Dev Portal IdP team mappings
- description: |
- Map existing developer teams from a third-party identity provider (IdP) and their permissions to elements in a {{site.konnect_short_name}} Dev Portal.
- icon: /assets/icons/team.svg
- cta:
- url: /dev-portal/team-mapping/
- - blocks:
- - type: card
- config:
- title: Dev Portal RBAC
- description: |
- Learn about Dev Portal pre-defined teams and roles for RBAC.
- icon: /assets/icons/rbac.svg
- cta:
- url: /dev-portal/developer-rbac/
-
- - header:
- type: h2
- text: "Application authentication strategies"
- columns:
- - blocks:
- - type: structured_text
- config:
- blocks:
- - type: text
- text: |
- Application authentication allows developers to authenticate with your API using credentials.
- Developers use the credentials from the authentication strategy when they use an API from your Dev Portal.
- You can define and reuse multiple authentication strategies for different APIs and Dev Portals.
-
- When you select an [authentication strategy](/dev-portal/auth-strategies/) during [API publication](/catalog/apis/) to a Dev Portal, {{site.konnect_short_name}} automatically applies the strategy to the linked Gateway Service.
-
- Dev Portal supports the following authentication strategies:
- * [Key authentication (`key-auth`)](/dev-portal/auth-strategies/#configure-the-key-auth-strategy)
- * [OpenID Connect (`oidc`)](/dev-portal/auth-strategies/#dev-portal-oidc-authentication)
- * [Dynamic Client Registration (DCR)](/dev-portal/dynamic-client-registration/)
-
- If a Gateway Service isn't associated with the API when you choose an authentication strategy, the settings are saved and applied once a Service is linked.
- If a Service is later unlinked, the authentication strategy is applied to the next linked Service.
- - header:
- type: h3
- text: "Get started with Dynamic Client Registration"
- columns:
- - blocks:
- - type: card
- config:
- title: Okta
- description: |
- Automatically create Dev Portal applications in Okta with Dynamic Client Registration
- icon: /assets/icons/okta.svg
- cta:
- url: /how-to/okta-dcr/
- - blocks:
- - type: card
- config:
- title: Azure AD
- description: |
- Automatically create and manage Dev Portal applications in Azure AD with Dynamic Client Registration
- icon: /assets/icons/azure.svg
- cta:
- url: /how-to/azure-ad-dcr/
- - blocks:
- - type: card
- config:
- title: Auth0
- description: |
- Automatically create and manage Dev Portal applications in Auth0 with Dynamic Client Registration
- icon: /assets/icons/third-party/auth0.svg
- cta:
- url: /how-to/auth0-dcr/
- - blocks:
- - type: card
- config:
- title: Curity
- description: |
- Automatically create and manage Dev Portal applications in Curity with Dynamic Client Registration
- icon: /assets/icons/third-party/curity.svg
- cta:
- url: /how-to/curity-dcr/
-
-
- - header:
- type: h2
- text: "Developer and application approvals"
- columns:
- - blocks:
- - type: structured_text
- config:
- blocks:
- - type: text
- text: |
- You can choose to auto approve developers and applications or require admin approval for developers and applications by navigating to **Settings** and the **Security** tab in your Dev Portal settings.
-
- If your settings require developer or application approval, you can manage approvals by navigating to **Access and approvals** in the sidebar. You need the [API Registration Approver and Portal Viewer role](/konnect-platform/teams-and-roles/#dev-portal) assigned to the Teams that control the APIs to approve these.
- Additionally, you can add developers to teams by clicking on the settings menu next to the name of the developer.
-
- Once approved, developers can create applications and view APIs, and the application can generate credentials to use the APIs.
-
- Applications and API keys are specific to a [geographic region](/konnect-platform/geos/).
- When you enable application registration by selecting an authentication strategy during publication, the resulting applications and API keys are tied to the developers and traffic in that region.
- - header:
- type: h3
- text: "Share applications with a team"
- columns:
- - blocks:
- - type: structured_text
- config:
- blocks:
- - type: text
- text: |
- You can assign an application to a team so that all members of that team share ownership of the application.
- Any team member can edit, manage, and use the application.
- Apps shared by a team appear in each member's apps in the Dev Portal.
- Team membership and roles are managed via [Dev Portal teams and roles](/dev-portal/developer-rbac/).
-
- This is useful in cases such as when a developer leaves your organization.
- With team application sharing, the team retains uninterrupted access to the application.
-
- Important considerations:
- * All members of the team that owns an application receive full ownership access.
- * Applications can only be transferred to teams that have [API Consumer](/dev-portal/developer-rbac/) access for every API currently registered by the application.
- Similarly, you can only register APIs to team-owned applications if everyone in the team has access to the API.
- This is true even if an individual team member has broader access through other teams.
-
- To enable team application sharing, navigate to your Dev Portal in {{site.konnect_short_name}} and click **Access and approvals > Teams**. Click the relevant team, go to **Settings**, and enable **Allow team to own applications**.
- To transfer ownership of an application to either a developer or team, navigate to the app and from the **Actions** dropdown menu, select "Transfer ownership".
-
- For more information about how to configure Dev Portal developer teams, see [Dev Portal RBAC](/dev-portal/developer-rbac/).
- For more information about the developer experience, see [Dev Portal developer sign-up](/dev-portal/developer-signup/#2-create-an-application).
- - header:
- type: h3
- text: "Limitations"
- columns:
- - blocks:
- - type: structured_text
- config:
- blocks:
- - type: text
- text: |
- Keep the following limitations in mind for developers and applications:
- * Each developer can create a maximum of 500 applications.
- * Each application can have a maximum of 20 API keys.
- * Each API that uses the [ACE plugin](/plugins/ace/) can have a maximum of 1,000 operations.
- * API Packages have a per-request PATCH limit of 100.
-
- - header:
- type: h2
- text: "Learn more"
- columns:
- - blocks:
- - type: card
- config:
- title: Dev Portal developer sign-up
- description: |
- Learn how developers can get started with the Dev Portal by registering and creating an application.
- cta:
- url: /dev-portal/developer-signup/
- - blocks:
- - type: card
- config:
- title: Application authentication strategies
- description: |
- Learn how to set up authentication strategies for application registration in Dev Portal.
- cta:
- url: /dev-portal/auth-strategies/
- - blocks:
- - type: card
- config:
- title: Dev Portal Dynamic Client Registration
- description: |
- Learn about supported DCR identity providers and supported DCR authentication methods.
- cta:
- url: /dev-portal/dynamic-client-registration/
-
- - header:
- text: "Frequently asked questions"
- type: h2
- columns:
- - blocks:
- - type: faqs
- config:
- - q: |
- {% include faqs/api-app-reg-override.md section='question' %}
- a: |
- {% include faqs/api-app-reg-override.md section='answer' %}
\ No newline at end of file
diff --git a/app/_landing_pages/event-gateway.yaml b/app/_landing_pages/event-gateway.yaml
index a878dd69fc..8661ad02d2 100644
--- a/app/_landing_pages/event-gateway.yaml
+++ b/app/_landing_pages/event-gateway.yaml
@@ -259,6 +259,14 @@ rows:
[OAuth](/identity/)
guide: |
[Set up {{site.event_gateway_short}} with {{site.identity}} OAuth](/event-gateway/kong-identity-oauth/)
+ - outcome: |
+ Automatically apply policies based on the metadata of connected clients, with no per-user rule changes needed
+ feature: |
+ [Modify Headers policy](/event-gateway/policies/modify-headers/)
+ guide: |
+ [Enrich SASL PLAIN connections with principal metadata](/event-gateway/kong-identity-metadata-integration/)
+
+ [Enrich OAuth connections with principal metadata](/event-gateway/kong-identity-jwt-metadata-integration/)
- outcome: |
Know exactly which teams and topics are driving cluster load, in real time, via in-platform dashboards or exported to your preferred tooling
feature: |
diff --git a/app/dev-portal/self-service.md b/app/dev-portal/self-service.md
new file mode 100644
index 0000000000..05c3a01abd
--- /dev/null
+++ b/app/dev-portal/self-service.md
@@ -0,0 +1,292 @@
+---
+title: "Developer self-service and application registration"
+content_type: reference
+layout: reference
+
+products:
+ - dev-portal
+tags:
+ - application-registration
+ - authentication
+
+works_on:
+ - konnect
+
+breadcrumbs:
+ - /dev-portal/
+
+api_specs:
+ - konnect/portal-management
+
+description: "Enable self-service registration flows for developers and applications using authentication strategies and {{site.konnect_short_name}} application auth."
+
+related_resources:
+ - text: "{{site.dev_portal}} developer sign-up"
+ url: /dev-portal/developer-signup/
+ - text: Application authentication strategies
+ url: /dev-portal/auth-strategies/
+ - text: "{{site.dev_portal}} Dynamic Client Registration"
+ url: /dev-portal/dynamic-client-registration/
+
+faqs:
+ - q: |
+ {% include faqs/api-app-reg-override.md section='question' %}
+ a: |
+ {% include faqs/api-app-reg-override.md section='answer' %}
+---
+
+{{site.konnect_short_name}} {{site.dev_portal}} provides flexible options for controlling access to content and APIs.
+When combined with a [Gateway Service](/gateway/entities/service/), developers visiting a {{site.dev_portal}} can sign up, create an application, register it with an API, and retrieve API keys without intervention from {{site.dev_portal}} administrators.
+
+Developer self-service consists of two main components:
+* **User authentication:** Allows users to access your {{site.dev_portal}} by logging in. You can further customize what logged in users can see using RBAC.
+* **Application registration:** Allows developers to use your APIs using credentials and create applications for them.
+
+## Enable developer self-service
+
+To enable developer self-service, do the following:
+1. Enable user authentication by navigating to **Settings > Security** in your {{site.dev_portal}}.
+
+ Developer sign ups and application creation require admin approval by default, which can also be configured in the {{site.dev_portal}} security settings.
+
+ For private {{site.dev_portal}}s, user authentication is enabled by default, and the default application auth strategy is key authentication.
+1. Configure an [application authentication strategy](/dev-portal/auth-strategies/) by navigating to **Settings > Security**.
+1. Optional: Enable [application sharing](#share-applications-with-a-team) for developer teams by navigating to your {{site.dev_portal}} in {{site.konnect_short_name}} and going to **Access and approvals > Teams**. Click the team, go to **Settings** and enable **Allow team to own applications**.
+1. Link an [API to a Gateway Service](/catalog/apis/#gateway-service-link).
+
+ This is required to enforce auth strategies.
+1. Publish an [API to a {{site.dev_portal}}](/catalog/apis/#publish-your-api-to-dev-portal).
+1. Select an authentication strategy when publishing the API to a {{site.dev_portal}}.
+1. For public content with restricted access, use [visibility settings](/dev-portal/pages-and-content/#page-visibility-and-publishing) to show public pages or APIs to anonymous users while restricting actions to logged-in users.
+
+## User authentication
+
+Enabling user authentication requires users to register with the {{site.dev_portal}}.
+You can decide which pages remain public and which ones require authentication.
+
+{{site.dev_portal}} supports the following user authentication types:
+* Basic authentication
+* OIDC
+* SAML
+
+Additionally, you can enable [RBAC](/dev-portal/developer-rbac/) from your {{site.dev_portal}}'s security settings to control who can view or view and consume APIs in your {{site.dev_portal}}.
+When RBAC is enabled, any {{site.dev_portal}} teams and roles you apply to a developer will control their access.
+
+To get started with user authentication, see the following how-tos:
+* [Configure {{site.dev_portal}} SSO](/dev-portal/sso/)
+* [{{site.dev_portal}} IdP team mappings](/dev-portal/team-mapping/)
+* [{{site.dev_portal}} RBAC](/dev-portal/developer-rbac/)
+
+## Application authentication strategies
+
+Application authentication allows developers to authenticate with your API using credentials.
+Developers use the credentials from the authentication strategy when they use an API from your {{site.dev_portal}}.
+You can define and reuse multiple authentication strategies for different APIs and {{site.dev_portal}}s.
+
+When you select an [authentication strategy](/dev-portal/auth-strategies/) during [API publication](/catalog/apis/) to a {{site.dev_portal}}, {{site.konnect_short_name}} automatically applies the strategy to the linked Gateway Service.
+
+{{site.dev_portal}} supports the following authentication strategies:
+* [Key authentication (`key-auth`)](/dev-portal/auth-strategies/#configure-the-key-auth-strategy)
+* [OpenID Connect (`oidc`)](/dev-portal/auth-strategies/#dev-portal-oidc-authentication)
+* [Dynamic Client Registration (DCR)](/dev-portal/dynamic-client-registration/)
+
+If a Gateway Service isn't associated with the API when you choose an authentication strategy, the settings are saved and applied once a Service is linked.
+If a Service is later unlinked, the authentication strategy is applied to the next linked Service.
+
+To automatically create and manage {{site.dev_portal}} applications using Dynamic Client Registration, see the following guides:
+
+{% html_tag type="div" css_classes="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3" %}
+{% icon_card icon="okta.svg" title="Okta" cta_url="/how-to/okta-dcr/" %}
+{% icon_card icon="azure.svg" title="Azure AD" cta_url="/how-to/azure-ad-dcr/" %}
+{% icon_card icon="third-party/auth0.svg" title="Auth0" cta_url="/how-to/auth0-dcr/" %}
+{% icon_card icon="third-party/curity.svg" title="Curity" cta_url="/how-to/curity-dcr/" %}
+{% endhtml_tag %}
+
+## Developer and application approvals
+
+You can choose to auto approve developers and applications or require admin approval for developers and applications by navigating to **Settings** and the **Security** tab in your {{site.dev_portal}} settings.
+
+If your settings require developer or application approval, you can manage approvals by navigating to **Access and approvals** in the sidebar. You need the [API Registration Approver and Portal Viewer role](/konnect-platform/teams-and-roles/#dev-portal) assigned to the Teams that control the APIs to approve these.
+Additionally, you can add developers to teams by clicking on the settings menu next to the name of the developer.
+
+Once approved, developers can create applications and view APIs, and the application can generate credentials to use the APIs.
+
+Applications and API keys are specific to a [geographic region](/konnect-platform/geos/).
+When you enable application registration by selecting an authentication strategy during publication, the resulting applications and API keys are tied to the developers and traffic in that region.
+
+### Share applications with a team
+
+You can assign an application to a team so that all members of that team share ownership of the application.
+Any team member can edit, manage, and use the application.
+Apps shared by a team appear in each member's apps in the {{site.dev_portal}}.
+Team membership and roles are managed via [{{site.dev_portal}} teams and roles](/dev-portal/developer-rbac/).
+
+This is useful in cases such as when a developer leaves your organization.
+With team application sharing, the team retains uninterrupted access to the application.
+
+Important considerations:
+* All members of the team that owns an application receive full ownership access.
+* Applications can only be transferred to teams that have [API Consumer](/dev-portal/developer-rbac/) access for every API currently registered by the application.
+ Similarly, you can only register APIs to team-owned applications if everyone in the team has access to the API.
+ This is true even if an individual team member has broader access through other teams.
+
+To enable team application sharing, navigate to your {{site.dev_portal}} in {{site.konnect_short_name}} and click **Access and approvals > Teams**. Click the relevant team, go to **Settings**, and enable **Allow team to own applications**.
+To transfer ownership of an application to either a developer or team, navigate to the app and from the **Actions** dropdown menu, select "Transfer ownership".
+
+For more information about how to configure {{site.dev_portal}} developer teams, see [{{site.dev_portal}} RBAC](/dev-portal/developer-rbac/).
+For more information about the developer experience, see [{{site.dev_portal}} developer sign-up](/dev-portal/developer-signup/#2-create-an-application).
+
+### Limitations
+
+Keep the following limitations in mind for developers and applications:
+* Each developer can create a maximum of 500 applications.
+* Each application can have a maximum of 20 API keys.
+* Each API that uses the [ACE plugin](/plugins/ace/) can have a maximum of 1,000 operations.
+* API Packages have a per-request PATCH limit of 100.
+
+## Apply plugins to applications {% new_in 3.15 %}
+
+You can apply {{site.base_gateway}} plugins to your {{site.dev_portal}} applications.
+This lets you enforce business logic, such as rate limiting or IP restriction, on the credentials that an application uses to access your APIs.
+
+The following table shows common use cases for applying plugins to applications:
+
+
+{% table %}
+columns:
+ - title: Use case
+ key: use_case
+ - title: Plugin
+ key: plugin
+rows:
+ - use_case: "Enforce request quotas on the credentials an application uses."
+ plugin: "[Rate Limiting](/plugins/rate-limiting/) or [Rate Limiting Advanced](/plugins/rate-limiting-advanced/)"
+ - use_case: "Ensure only requests from a partner's known IP ranges can use their application credentials."
+ plugin: "[IP Restriction](/plugins/ip-restriction/)"
+ - use_case: "Automatically inject a header identifying the partner into every request their application makes, so your upstream can route or log by customer without trusting client-supplied headers."
+ plugin: "[Request Transformer](/plugins/request-transformer/)"
+{% endtable %}
+
+
+Plugins can be applied to an application by either linking the application to an existing Consumer in {{site.base_gateway}} or applying the plugin via conditional execution logic to the associated principal.
+When a developer creates an application, {{site.dev_portal}} automatically creates a {{site.identity}} principal for this application.
+This principal entity then helps link the application to an existing Consumer entity in {{site.base_gateway}}.
+{{site.identity}} doesn't store any credentials for applications. It is only used for mapping an application to a Consumer.
+
+You can apply plugins to an application in two different ways:
+
+
+{% table %}
+columns:
+ - title: Method
+ key: method
+ - title: Existing Consumers for {{site.dev_portal}} applications
+ key: existing_consumers
+ - title: Plugin mapped to
+ key: mapped_to
+ - title: Description
+ key: description
+rows:
+ - method: "Conditional plugin execution"
+ existing_consumers: "No"
+ mapped_to: "Principal"
+ description: "Use [conditional plugin execution](/gateway/configure-conditional-plugin-execution/) with an expression that references the application's `principal.id`. You can manage the principal configuration in {{site.identity}} and the plugin configuration in Gateway Manager."
+ - method: "Consumer-scoped plugins"
+ existing_consumers: "Yes"
+ mapped_to: "Consumer"
+ description: "[Map the application to an existing Gateway Consumer](#map-an-application-to-a-consumer), then configure Consumer-scoped plugins on that Consumer. This is a common starting point if you already have Consumers configured."
+{% endtable %}
+
+
+### Apply a plugin to an application using a {{site.identity}} principal
+
+This method applies the plugin based on the application's principal, without requiring you to map the application to a Gateway Consumer.
+The plugin runs whenever a request authenticates as that application, using a [conditional plugin execution](/gateway/configure-conditional-plugin-execution/) expression that references the application's `principal.id`.
+
+In this example, we'll use the [Rate Limiting Advanced](/plugins/rate-limiting-advanced/) plugin, but you can apply any plugin to an application's principal with `principal.id`.
+
+1. List the applications in your portal, filtering by the application's name, and capture its ID as the `PRINCIPAL_ID` variable. Replace `$APPLICATION_NAME` with the name of your application:
+{% capture copy-app-id %}
+
+{% konnect_api_request %}
+url: /v3/portals/$PORTAL_ID/applications?filter%5Bname%5D%5Beq%5D=$APPLICATION_NAME
+status_code: 200
+region: us
+method: GET
+capture:
+ - variable: PRINCIPAL_ID
+ jq: '.data[0].id'
+{% endkonnect_api_request %}
+
+{% endcapture %}
+{{ copy-app-id | indent: 3}}
+ The principal ID is the same as the application's ID.
+1. Configure the Rate Limiting Advanced plugin and use a conditional plugin execution expression to apply it to the application's principal. Replace `$CONTROL_PLANE_ID` with the ID of the control plane that your API is linked to:
+{% capture apply-plugin %}
+
+{% konnect_api_request %}
+url: /v2/control-planes/$CONTROL_PLANE_ID/core-entities/plugins/
+status_code: 201
+region: us
+method: POST
+body:
+ name: rate-limiting-advanced
+ config:
+ limit:
+ - 200
+ window_size:
+ - 1800
+ window_type: fixed
+ namespace: my-namespace
+ condition: 'principal.id == "$PRINCIPAL_ID"'
+{% endkonnect_api_request %}
+
+{% endcapture %}
+{{ apply-plugin | indent: 3}}
+Any request that authenticates as this application is now rate limited to 200 requests every 30 minutes.
+
+### Map an application to a Consumer
+
+Mapping an application to a Consumer requires either the [Control Plane Admin or Consumer Admin](/konnect-platform/teams-and-roles/#control-planes) roles, granted for each API instance registered by the application.
+
+{% navtabs "map-consumer" %}
+{% navtab "API" %}
+To map an application to an existing Consumer, send a `PUT` request to the registration's `consumer` endpoint with the ID of the Gateway Consumer:
+
+
+{% konnect_api_request %}
+url: /v3/portals/$PORTAL_ID/applications/$APPLICATION_ID/registrations/$REGISTRATION_ID/consumer
+status_code: 204
+region: us
+method: PUT
+body:
+ id: $CONSUMER_ID
+{% endkonnect_api_request %}
+
+
+{% endnavtab %}
+{% navtab "UI" %}
+1. In the {{site.konnect_short_name}} sidebar, click **Dev Portal > Portals**.
+1. Click your portal.
+1. Click the **Access and approvals** tab.
+1. Click the **App Registrations** tab.
+1. Click the application you want to link a Consumer to.
+1. In the **App Registrations** section, click the action menu icon for the registration.
+1. Click **Link Consumer**.
+1. From the **Consumer** dropdown menu, select the Consumer you want to link.
+ The API must be [linked to a Gateway Service or control plane](/catalog/apis/#allow-developers-to-consume-your-api) to link a Consumer.
+1. Click **Link consumer**.
+{% endnavtab %}
+{% endnavtabs %}
+
+Any plugins that were applied to the Consumer are now applied to the {{site.dev_portal}} application.
+
+### Limitations
+
+Keep the following in mind when you map applications to Consumers or principals:
+* Both the [KAA and ACE plugins](/catalog/apis/#allow-developers-to-consume-your-api) look up principals to resolve the Consumer mapped to an application.
+* An application maps to a single Consumer (a 1:1 mapping through one principal).
+* If you're mapping applications to Consumers, the Consumer must already exist. The {{site.dev_portal}} validates that the Consumer exists on the Gateway before it will be mapped.
+* Application registrations for APIs that are linked to the same Gateway Service will share the same effective Consumer mapping.
+ Updating the mapping for one registration updates it for all registrations that resolve to the same Gateway context.
+* Applying plugins to applications is only available on v3 {{site.dev_portal}}s.
diff --git a/app/event-gateway/entities/virtual-cluster.md b/app/event-gateway/entities/virtual-cluster.md
index 6ecd7d86c2..cbae78c237 100644
--- a/app/event-gateway/entities/virtual-cluster.md
+++ b/app/event-gateway/entities/virtual-cluster.md
@@ -177,6 +177,20 @@ Choose the mode based on your security requirements and backend cluster configur
* Terminate (`terminate`): Checks whether the client’s connection is authorized based on their credential, and then terminates the authentication. Then, a new authentication session starts with the backend cluster.
* Validate and forward (`validate_forward`): The client’s OAuth token is first validated by the proxy, and then sent to the backend as-is. This will “fail fast” if the token is invalid before sending it to the backend.
+### Enrich connections with caller metadata
+
+After a client authenticates, you can configure the virtual cluster to look up the client in a {{site.identity}} directory and attach its metadata to the connection context.
+This metadata is available at `context.auth.principal.metadata` and can be used to drive policies automatically.
+For example, setting a header based on which team a client belongs to, or skipping records that a client isn’t entitled to see.
+
+To enable this, add a `fetch_kong_identity_principal` block to the `authentication` config.
+For SASL/PLAIN connections, specify a `fetch_by.key` matching the identity type (for example, `sasl_username`).
+For OAuth Bearer connections, the lookup uses the JWT’s `iss` and `sub` claims automatically.
+
+See the following how-to guides for end-to-end examples:
+- [Enrich SASL PLAIN connections with principal metadata](/event-gateway/kong-identity-metadata-integration/)
+- [Enrich OAuth connections with principal metadata](/event-gateway/kong-identity-jwt-metadata-integration/)
+
## Namespaces
With namespaces, you can preserve any naming systems that you have in place, and ensure they remain consistent.