This is an automated email from the ASF dual-hosted git repository.

fjtiradosarti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-kogito-docs.git


The following commit(s) were added to refs/heads/main by this push:
     new 05aad84ad [Fix apache/incubator-kie-issues#1900] Add doc for token 
exchange (#737)
05aad84ad is described below

commit 05aad84adda15676cc5cd613d7db387693446456
Author: gabriel-farache <[email protected]>
AuthorDate: Thu Sep 18 10:54:25 2025 +0200

    [Fix apache/incubator-kie-issues#1900] Add doc for token exchange (#737)
    
    Assisted by Cursor - gpt-5
    
    Signed-off-by: gabriel-farache <[email protected]>
---
 .../images/security/token-exchange-sequence.svg    |  90 ++++++
 serverlessworkflow/modules/ROOT/nav.adoc           |   1 +
 serverlessworkflow/modules/ROOT/pages/index.adoc   |   8 +
 .../token-exchange-for-openapi-services.adoc       | 310 +++++++++++++++++++++
 4 files changed, 409 insertions(+)

diff --git 
a/serverlessworkflow/modules/ROOT/assets/images/security/token-exchange-sequence.svg
 
b/serverlessworkflow/modules/ROOT/assets/images/security/token-exchange-sequence.svg
new file mode 100644
index 000000000..91eba221d
--- /dev/null
+++ 
b/serverlessworkflow/modules/ROOT/assets/images/security/token-exchange-sequence.svg
@@ -0,0 +1,90 @@
+<svg viewBox="0 0 1100 600" xmlns="http://www.w3.org/2000/svg"; role="img" 
aria-labelledby="title desc">
+  <title id="title">Token Exchange sequence</title>
+  <desc id="desc">Sequence including cache check, token exchange, and 
proactive refresh between Client, Workflow, OIDC Client Filter (with Token 
Cache), Identity Provider and Downstream Service</desc>
+  <defs>
+    <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" 
markerHeight="7" orient="auto-start-reverse">
+      <path d="M 0 0 L 10 5 L 0 10 z" fill="#333" />
+    </marker>
+    <style>
+      text { font-family: Arial, Helvetica, sans-serif; font-size: 16px; fill: 
#222; }
+      .title { font-weight: bold; }
+      .lane { fill: #f8f9fa; stroke: #d0d7de; }
+      .lifeline { stroke: #c0c7cf; stroke-dasharray: 4 4; }
+      .arrow { stroke: #333; stroke-width: 1.5; fill: none; marker-end: 
url(#arrow); }
+    </style>
+  </defs>
+
+  <!-- Participant positions -->
+  <g transform="translate(0,0)">
+    <!-- X positions: 100, 300, 500, 700, 900 -->
+    <g transform="translate(100,20)">
+      <rect class="lane" x="-70" y="0" width="140" height="30" rx="4"/>
+      <text class="title" x="0" y="20" text-anchor="middle">Client</text>
+      <line class="lifeline" x1="0" y1="40" x2="0" y2="560"/>
+    </g>
+    <g transform="translate(300,20)">
+      <rect class="lane" x="-90" y="0" width="180" height="30" rx="4"/>
+      <text class="title" x="0" y="20" text-anchor="middle">Workflow</text>
+      <line class="lifeline" x1="0" y1="40" x2="0" y2="560"/>
+    </g>
+    <g transform="translate(500,20)">
+      <rect class="lane" x="-120" y="0" width="240" height="30" rx="4"/>
+      <text class="title" x="0" y="20" text-anchor="middle">OIDC Client 
Filter</text>
+      <line class="lifeline" x1="0" y1="40" x2="0" y2="560"/>
+    </g>
+    <g transform="translate(700,20)">
+      <rect class="lane" x="-120" y="0" width="240" height="30" rx="4"/>
+      <text class="title" x="0" y="20" text-anchor="middle">Identity 
Provider</text>
+      <line class="lifeline" x1="0" y1="40" x2="0" y2="560"/>
+    </g>
+    <g transform="translate(900,20)">
+      <rect class="lane" x="-120" y="0" width="240" height="30" rx="4"/>
+      <text class="title" x="0" y="20" text-anchor="middle">Downstream 
Service</text>
+      <line class="lifeline" x1="0" y1="40" x2="0" y2="560"/>
+    </g>
+  </g>
+
+  <!-- Messages -->
+  <!-- 1: Client -> Workflow -->
+  <line class="arrow" x1="100" y1="110" x2="300" y2="110"/>
+  <text x="200" y="100" text-anchor="middle">1) POST /workflow</text>
+  <text x="200" y="125" text-anchor="middle">Authorization: Bearer 
user_access_token</text>
+
+  <!-- 2: Workflow -> OIDC Client Filter -->
+  <line class="arrow" x1="300" y1="160" x2="500" y2="160"/>
+  <text x="400" y="150" text-anchor="middle">2) Invoke OpenAPI client</text>
+
+  <!-- 3: OIDC Client Filter self-message: cache check -->
+  <path class="arrow" d="M 500 200 h 80 v 20 h -80" />
+  <text x="540" y="195" text-anchor="middle">3) Check cache (reuse if 
valid)</text>
+
+  <!-- 4: OIDC Client Filter -> Identity Provider (if miss/near expiry) -->
+  <line class="arrow" x1="500" y1="250" x2="700" y2="250"/>
+  <text x="600" y="240" text-anchor="middle">4) Token Exchange</text>
+  <text x="600" y="265" text-anchor="middle">grant_type=token-exchange, 
subject_token=user_access_token, audience=downstream-api</text>
+
+  <!-- 5: Identity Provider -> OIDC Client Filter -->
+  <line class="arrow" x1="700" y1="300" x2="500" y2="300"/>
+  <text x="600" y="290" text-anchor="middle">5) access_token 
(aud=downstream-api)</text>
+
+  <!-- 6: OIDC Client Filter -> Downstream Service -->
+  <line class="arrow" x1="500" y1="350" x2="900" y2="350"/>
+  <text x="700" y="340" text-anchor="middle">6) GET /secured</text>
+  <text x="700" y="365" text-anchor="middle">Authorization: Bearer 
exchanged_access_token</text>
+
+  <!-- 7: Downstream Service -> OIDC Client Filter -->
+  <line class="arrow" x1="900" y1="400" x2="500" y2="400"/>
+  <text x="700" y="390" text-anchor="middle">7) 200 OK</text>
+
+  <!-- 8: OIDC Client Filter -> Workflow -->
+  <line class="arrow" x1="500" y1="450" x2="300" y2="450"/>
+  <text x="400" y="440" text-anchor="middle">8) 200 OK</text>
+
+  <!-- 9: Workflow -> Client -->
+  <line class="arrow" x1="300" y1="500" x2="100" y2="500"/>
+  <text x="200" y="490" text-anchor="middle">9) Result</text>
+
+  <!-- 10: Proactive refresh (background) -->
+  <line class="arrow" x1="500" y1="540" x2="700" y2="540" 
style="stroke-dasharray:5 5"/>
+  <text x="600" y="530" text-anchor="middle">10) Proactive refresh (background 
monitor)</text>
+</svg>
diff --git a/serverlessworkflow/modules/ROOT/nav.adoc 
b/serverlessworkflow/modules/ROOT/nav.adoc
index bce7d877c..8325af0e1 100644
--- a/serverlessworkflow/modules/ROOT/nav.adoc
+++ b/serverlessworkflow/modules/ROOT/nav.adoc
@@ -81,6 +81,7 @@
 ** Client Authentication
 *** xref:security/authention-support-for-openapi-services.adoc[OpenAPI 
Authentication]
 *** xref:security/orchestrating-third-party-services-with-oauth2.adoc[OpenAPI 
OAuth2]
+*** xref:security/token-exchange-for-openapi-services.adoc[Token Exchange]
 * Executing, Testing and Troubleshooting
 ** xref:testing-and-troubleshooting/kn-plugin-workflow-overview.adoc[KN CLI 
Workflow plugin]
 ** 
xref:testing-and-troubleshooting/quarkus-dev-ui-extension/quarkus-dev-ui-overview.adoc[Developer
 UI]
diff --git a/serverlessworkflow/modules/ROOT/pages/index.adoc 
b/serverlessworkflow/modules/ROOT/pages/index.adoc
index c6d15e984..4fd99bd33 100644
--- a/serverlessworkflow/modules/ROOT/pages/index.adoc
+++ b/serverlessworkflow/modules/ROOT/pages/index.adoc
@@ -237,6 +237,14 @@ 
xref:security/orchestrating-third-party-services-with-oauth2.adoc[]
 Learn about the OAuth2 method support when orchestrating REST services using 
your workflow application
 --
 
+[.card]
+--
+[.card-title]
+xref:security/token-exchange-for-openapi-services.adoc[]
+[.card-description]
+Learn how to configure OAuth 2.0 Token Exchange to call OpenAPI-secured 
services without forwarding end-user tokens
+--
+
 [.card-section]
 == Executing, Testing and Troubleshooting
 
diff --git 
a/serverlessworkflow/modules/ROOT/pages/security/token-exchange-for-openapi-services.adoc
 
b/serverlessworkflow/modules/ROOT/pages/security/token-exchange-for-openapi-services.adoc
new file mode 100644
index 000000000..589b21f2a
--- /dev/null
+++ 
b/serverlessworkflow/modules/ROOT/pages/security/token-exchange-for-openapi-services.adoc
@@ -0,0 +1,310 @@
+= Token Exchange for OpenAPI services in {product_name}
+:compat-mode!:
+// Metadata:
+:description: OAuth 2.0 Token Exchange for OpenAPI service invocations
+:keywords: kogito, workflow, serverless, OAuth2, OIDC, token exchange
+// Referenced documentation pages.
+:orchestration-of-openapi-based-services: 
xref:service-orchestration/orchestration-of-openapi-based-services.adoc
+:configuring-openapi-services-endpoints: 
xref:service-orchestration/configuring-openapi-services-endpoints.adoc
+:authentication-support-for-openapi-services: 
xref:security/authention-support-for-openapi-services.adoc
+
+This guide shows how to configure OAuth 2.0 Token Exchange when invoking 
OpenAPI-secured services from workflows.
+
+For general OpenAPI orchestration and authentication, see:
+
+* {orchestration-of-openapi-based-services}[Orchestrating the OpenAPI services]
+* {configuring-openapi-services-endpoints}[Configuring the OpenAPI services 
endpoints]
+* {authentication-support-for-openapi-services}[Authentication for OpenAPI 
services]
+
+
+== When to use Token Exchange
+
+Use Token Exchange if:
+
+* You must avoid forwarding the original end-user token to third-party 
services.
+* You have long running workflow which may cause token invalidity (expiration).
+
+If you only need to forward the original token, see token propagation in 
{authentication-support-for-openapi-services}#ref-authorization-token-propagation[Authorization
 token propagation].
+
+== How Token Exchange works
+
+At a high level, Token Exchange lets the workflow swap the incoming bearer 
token for a new token tailored to the downstream service.
+The Quarkus OIDC Client Filter performs the exchange transparently before the 
OpenAPI client call. See 
link:https://quarkus.io/guides/security-openid-connect-client[Quarkus OIDC 
Client Filter] for more details.
+
+.Flow overview
+[%autowidth,cols="1,5"]
+|===
+|Step |Description
+
+|1 |Client calls the workflow REST endpoint and sends `Authorization: Bearer 
<user-access-token>`.
+|2 |The OpenAPI client (generated for a secured operation) is invoked by the 
workflow.
+|3 |The credential provider checks the cache for a valid exchanged token for 
this process instance and auth name; if found and not near expiry, it is reused.
+|4 |If no valid token is found, the OIDC Client Filter detects the `oauth2` 
security scheme and requests a Token Exchange at the IdP, using the incoming 
token as the subject token and the configured audience.
+|5 |The Identity Provider returns the exchanged access token (for example, 
audience `downstream-api`), which is cached with its expiry.
+|6 |The OpenAPI client calls the downstream service with `Authorization: 
Bearer <exchanged-token>`.
+|7 |Downstream service responds; workflow proceeds and returns the result to 
the client.
+|8 |Proactive refresh: a background monitor refreshes cached tokens nearing 
expiration based on 
`sonataflow.security.auth.<auth_name>.token-exchange.proactive-refresh-seconds` 
and `sonataflow.security.auth.token-exchange.monitor-rate-seconds`.
+|===
+
+.Sequence diagram
+link:images/security/token-exchange-sequence.svg[image:security/token-exchange-sequence.svg[width=100%],role="diagram"]
+
+== Requirements
+
+* Quarkus OIDC Client Filter extension in the workflow service:
+
+[source,xml]
+----
+<dependency>
+  <groupId>io.quarkus</groupId>
+  <artifactId>quarkus-oidc-client-filter</artifactId>
+</dependency>
+----
+
+* The OpenAPI operation is secured with an `oauth2` security scheme (the OIDC 
client name is derived from the scheme name).
+
+* Quarkus add-on to enable token exchange and caching in your runtime:
+
+[source,xml]
+----
+<dependency>
+  <groupId>org.kie</groupId>
+  <artifactId>kie-addons-quarkus-token-exchange</artifactId>
+</dependency>
+----
+
+Those extensions should be passed to the internal builder when building the 
workflow image, see 
xref:cloud/operator/build-and-deploy-workflows.adoc#passing-build-arguments-to-internal-workflow-builder[Passing
 arguments to the internal builder]
+
+== Example OpenAPI security
+
+[source, yaml]
+----
+openapi: 3.0.3
+paths:
+  /secured:
+    get:
+      operationId: callService
+      responses:
+        "200":
+          description: OK
+      security:
+        - service-oauth: [ ]
+components:
+  securitySchemes:
+    service-oauth:
+      type: oauth2
+      flows:
+        clientCredentials:
+          authorizationUrl: 
https://idp.example.com/realms/acme/protocol/openid-connect/auth
+          tokenUrl: 
https://idp.example.com/realms/acme/protocol/openid-connect/token
+          scopes: {}
+----
+
+The security scheme name `service-oauth` determines the OIDC client name 
(sanitized to `service_oauth`) used by the client filter.
+
+
+== Caching and persistence of exchanged tokens
+
+The Token Exchange feature introduces a caching mechanism with proactive 
refresh and optional database persistence.
+
+=== What is loaded by default
+
+Without extra configuration, the add-on provides:
+
+* In-memory cache using Caffeine with per-token expiration.
+* Proactive refresh handled by a background monitor.
+
+Enable the feature per auth scheme name and optionally tune refresh/monitor:
+
+[source,properties]
+----
+# Enable Token Exchange for a specific auth name (matches the OpenAPI oauth2 
scheme name after sanitization)
+sonataflow.security.auth.service_oauth.token-exchange.enabled=true
+
+# Seconds before token expiration to proactively refresh the cached token 
(default ~300)
+sonataflow.security.auth.service_oauth.token-exchange.proactive-refresh-seconds=300
+
+# Global monitor rate (seconds) for the cache refresh/cleanup
+sonataflow.security.auth.token-exchange.monitor-rate-seconds=60
+
+# To ensure the incoming `Authorization` header is available when a workflow 
waits and later resumes (or after service restarts), enable header persistence:
+kogito.persistence.headers.enabled=true
+----
+
+
+=== Persist exchanged tokens (override default)
+
+By default, the cache metadata is kept in-memory. To persist exchanged tokens, 
include the JDBC token persistence extension which provides a CDI 
`TokenCacheRepository` backed by a JDBC `DataSource`:
+
+[source,xml]
+----
+<dependency>
+  <groupId>org.kie</groupId>
+  
<artifactId>kogito-quarkus-serverless-workflow-jdbc-token-persistence</artifactId>
+</dependency>
+----
+
+The extension should be passed to the internal builder when building the 
workflow image, see 
xref:cloud/operator/build-and-deploy-workflows.adoc#passing-build-arguments-to-internal-workflow-builder[Passing
 arguments to the internal builder]
+
+
+You can also provide your own implementation by producing a CDI bean of type 
`org.kie.kogito.addons.quarkus.token.exchange.persistence.TokenCacheRepository`.
 When present, it overrides the default in-memory repository.
+
+=== How caching works
+
+* The OpenAPI credential provider computes a cache key per request (process 
instance, auth name, subject token, audience) and checks the cache.
+* On miss, it exchanges the token via the configured OIDC client and stores 
the result alongside expiration/refresh metadata.
+* An expiry policy evicts tokens at their individual expiration time; an 
eviction handler coordinates proactive refresh.
+
+== Configuration
+
+Configure the OIDC client and enable Token Exchange per OpenAPI security 
scheme. The client filter will obtain the incoming bearer token and exchange it 
for a new token before invoking the OpenAPI client generated for the secured 
operation.
+
+[source,properties]
+----
+# 1) Generated client package and base URL (example)
+#    Replace 'service_api_yaml' with your OpenAPI file id (sanitized filename)
+quarkus.rest-client.service_api_yaml.url=http://localhost:8480
+
+# 2) Enable Token Exchange for the OpenAPI oauth2 scheme defined as 
'service-oauth'
+#    (sanitized auth name is 'service_oauth')
+# see Configuration reference for more possible properties
+sonataflow.security.auth.service_oauth.token-exchange.enabled=true
+
+# 3) OIDC client for the service-oauth scheme (normalized to service_oauth)
+# Should be updated with your own values
+quarkus.oidc-client.service_oauth.discovery-enabled=false
+quarkus.oidc-client.service_oauth.auth-server-url=https://idp.example.com/realms/acme/protocol/openid-connect/auth
+quarkus.oidc-client.service_oauth.token-path=https://idp.example.com/realms/acme/protocol/openid-connect/token
+quarkus.oidc-client.service_oauth.client-id=kogito-app
+quarkus.oidc-client.service_oauth.grant.type=exchange
+quarkus.oidc-client.service_oauth.credentials.client-secret.method=basic
+quarkus.oidc-client.service_oauth.credentials.client-secret.value=secret
+----
+
+[NOTE]
+====
+* The incoming request to the workflow must include `Authorization: Bearer 
<user-access-token>` so the client filter can perform the exchange.
+* If you also need token propagation (forward the incoming token), configure 
it per service and auth name. For the example above:
+** 
`quarkus.openapi-generator.service_api_yaml.auth.service_oauth.token-propagation=true`
+* If both exchange and propagation are enables for the same scheme, token 
propagation takes precedence a no exchange will be performed. 
+This behaviour is brought by the openapi-generator library with custom 
`CredentialsProvider` implementations.
+====
+
+=== Configuration reference
+
+.Summary of configurable properties
+[cols="35%,45%,10%,10%", options="header"]
+|===
+|Property key |Usage |Default |Mandatory
+
+|`sonataflow.security.auth.<auth_name>.token-exchange.enabled`
+|Enable OAuth2 token exchange for the oauth2 security scheme `<auth_name>` 
(sanitized from OpenAPI scheme name, for example `service_oauth`).
+|`false`
+|No
+
+|`sonataflow.security.auth.<auth_name>.token-exchange.proactive-refresh-seconds`
+|Seconds before token expiration to proactively refresh the cached exchanged 
token.
+|`300`
+|No
+
+|`sonataflow.security.auth.token-exchange.monitor-rate-seconds`
+|Global schedule period (seconds) for cache refresh/cleanup across all auth 
names.
+|`60`
+|No
+
+|`quarkus.oidc-client.<auth_name>.auth-server-url`
+|OIDC authorization server URL for the token endpoint (from your IdP).
+|n/a
+|Conditional
+
+|`quarkus.oidc-client.<auth_name>.token-path`
+|Token endpoint path or full URL.
+|n/a
+|Conditional
+
+|`quarkus.oidc-client.<auth_name>.discovery-enabled`
+|Use OIDC discovery. Set to `false` when configuring URLs explicitly.
+|`true`
+|No
+
+|`quarkus.oidc-client.<auth_name>.client-id`
+|OIDC client identifier used for exchange.
+|n/a
+|Yes
+
+|`quarkus.oidc-client.<auth_name>.grant.type`
+|Must be set to `exchange` to enable OAuth2 Token Exchange.
+|n/a
+|Yes
+
+|`quarkus.oidc-client.<auth_name>.credentials.client-secret.method`
+|Client secret authentication method used by the OIDC client.
+|`basic`
+|No
+
+|`quarkus.oidc-client.<auth_name>.credentials.client-secret.value`
+|Client secret value used by the OIDC client.
+|n/a
+|Conditional
+
+|`quarkus.openapi-generator.<service_id>.auth.<auth_name>.token-propagation`
+|Propagate the incoming token to downstream calls for service `<service_id>` 
and auth `<auth_name>` (optional, separate from exchange).
+|`false`
+|No
+
+|`quarkus.openapi-generator.<service_id>.auth.<auth_name>.header-name`
+|Header to read the incoming token from when propagating.
+|`Authorization`
+|No
+
+|`kogito.persistence.headers.enabled`
+|Persist inbound HTTP headers with the workflow instance so the 
`Authorization` header is available across wait/resume and restarts 
(recommended when using token exchange/propagation).
+|`false`
+|No
+
+|`quarkus.rest-client.<service_id>.url`
+|Base URL for generated REST client calls.
+|n/a
+|Yes
+|===
+
+[NOTE]
+====
+"Conditional" means the property is required only in certain setups:
+
+* For `quarkus.oidc-client.<auth_name>.auth-server-url` and 
`quarkus.oidc-client.<auth_name>.token-path`:
+** If `discovery-enabled=true`, the client discovers endpoints from the 
issuer, so `token-path` is not required.
+** If `discovery-enabled=false`, you must provide `token-path` and an 
authorization server URL. Some environments allow `token-path` as an absolute 
URL, otherwise set both.
+* For `quarkus.oidc-client.<auth_name>.credentials.client-secret.value`: 
required only when the client uses a secret-based authentication method (for 
example, `client-secret-basic` or `client-secret-post`). Not required for 
public clients or when using non-secret methods such as mTLS or 
`private_key_jwt`.
+
+References:
+
+* Quarkus OIDC Client: https://quarkus.io/guides/security-openid-connect-client
+* OIDC Client Filter (REST Client): 
https://quarkus.io/guides/security-openid-connect-client#rest-client-oidc-client-filter
+* Quarkus OpenAPI Generator: 
https://docs.quarkiverse.io/quarkus-openapi-generator/dev/client.html
+* Quarkus OIDC Client Filter: 
https://quarkus.io/guides/security-openid-connect-client
+====
+
+== Workflow invocation example
+
+Send the user’s token to the workflow; the OpenAPI call secured by 
`service-oauth` will use the exchanged token automatically:
+
+[source,bash]
+----
+curl -X POST \
+  http://localhost:8080/my_workflow \
+  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
+  -H "Content-Type: application/json" \
+  -d '{"input":"value"}'
+----
+
+== Interaction with OpenAPI configuration
+
+* Security scheme names in the OpenAPI file are global. All operations secured 
by `service-oauth` will use the same OIDC client and Token Exchange 
configuration.
+* You can still use the standard OpenAPI Generator properties for codegen and 
base URLs as usual.
+
+== Additional resources
+
+* {authentication-support-for-openapi-services}[Authentication for OpenAPI 
services]
+* {orchestration-of-openapi-based-services}[Orchestrating the OpenAPI services]
+* link:https://www.keycloak.org/securing-apps/token-exchange[Keycloak Token 
Exchange]
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to