You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2025/02/27 09:29:01 UTC

(camel) 25/41: CAMEL-21760: OpenID Connect & OAuth for Camel Kafka, Web, Rest (#17196)

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

acosentino pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit a9fd20a76618c0ba197af19fdbfb563c2ddde151
Author: Thomas Diesler <td...@redhat.com>
AuthorDate: Thu Feb 27 06:45:19 2025 +0100

    CAMEL-21760: OpenID Connect & OAuth for Camel Kafka, Web, Rest (#17196)
---
 bom/camel-bom/pom.xml                              |    5 +
 catalog/camel-allcomponents/pom.xml                |    5 +
 .../org/apache/camel/catalog/others.properties     |    1 +
 .../org/apache/camel/catalog/others/oauth.json     |   13 +
 components/camel-oauth/README.md                   |  101 +
 components/camel-oauth/helm/Chart.yaml             |   21 +
 components/camel-oauth/helm/etc/camel-realm.json   | 2139 ++++++++++++++++++++
 components/camel-oauth/helm/etc/keycloak.crt       |   33 +
 components/camel-oauth/helm/etc/keycloak.key       |   52 +
 components/camel-oauth/helm/etc/san.cnf            |   22 +
 .../camel-oauth/helm/templates/keycloak.yaml       |  127 ++
 components/camel-oauth/helm/values-keycloak.yaml   |   25 +
 components/camel-oauth/pom.xml                     |  133 ++
 .../services/org/apache/camel/other.properties     |    7 +
 .../camel-oauth/src/generated/resources/oauth.json |   13 +
 .../apache/camel/oauth/AbstractOAuthProcessor.java |   80 +
 .../apache/camel/oauth/AuthCodeCredentials.java    |   41 +
 .../org/apache/camel/oauth/ClientCredentials.java  |   41 +
 .../java/org/apache/camel/oauth/Credentials.java   |   21 +
 .../org/apache/camel/oauth/InMemorySession.java    |   60 +
 .../apache/camel/oauth/InMemorySessionStore.java   |   96 +
 .../main/java/org/apache/camel/oauth/OAuth.java    |   72 +
 .../camel/oauth/OAuthBearerTokenProcessor.java     |   78 +
 .../oauth/OAuthClientCredentialsProcessor.java     |   83 +
 .../oauth/OAuthCodeFlowCallbackProcessor.java      |   74 +
 .../apache/camel/oauth/OAuthCodeFlowParams.java    |  167 ++
 .../apache/camel/oauth/OAuthCodeFlowProcessor.java |   75 +
 .../java/org/apache/camel/oauth/OAuthConfig.java   |  111 +
 .../org/apache/camel/oauth/OAuthException.java     |   29 +
 .../java/org/apache/camel/oauth/OAuthFactory.java  |   46 +
 .../java/org/apache/camel/oauth/OAuthFlowType.java |   25 +
 .../org/apache/camel/oauth/OAuthLogoutParams.java  |   41 +
 .../apache/camel/oauth/OAuthLogoutProcessor.java   |   53 +
 .../org/apache/camel/oauth/OAuthProperties.java    |   35 +
 .../java/org/apache/camel/oauth/OAuthSession.java  |   42 +
 .../org/apache/camel/oauth/OAuthSessionStore.java  |   28 +
 .../org/apache/camel/oauth/TokenCredentials.java   |   31 +
 .../org/apache/camel/oauth/UserCredentials.java    |   30 +
 .../java/org/apache/camel/oauth/UserProfile.java   |  115 ++
 .../org/apache/camel/oauth/vertx/VertxOAuth.java   |  198 ++
 .../camel/oauth/vertx/VertxOAuthFactory.java       |   39 +
 .../apache/camel/oauth/vertx/VertxUserProfile.java |   48 +
 .../src/main/resources/log4j2.properties           |   28 +
 .../camel/test/oauth/AbstractKeycloakTest.java     |   66 +
 .../org/apache/camel/test/oauth/KeycloakAdmin.java |  234 +++
 .../camel/test/oauth/KeycloakOAuthRestTest.java    |  123 ++
 .../camel/test/oauth/KeycloakOAuthWebAppTest.java  |  118 ++
 .../apache/camel/test/oauth/SSLCertTrustTest.java  |   53 +
 .../camel-oauth/src/test/resources/index.html      |   35 +
 .../camel-oauth/src/test/resources/protected.html  |   35 +
 .../camel-oauth/src/test/resources/styles.css      |   24 +
 components/pom.xml                                 |    1 +
 .../modules/others/examples/json/oauth.json        |    1 +
 parent/pom.xml                                     |    5 +
 54 files changed, 5179 insertions(+)

diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml
index e0f390316aa..3fb91b60b38 100644
--- a/bom/camel-bom/pom.xml
+++ b/bom/camel-bom/pom.xml
@@ -1522,6 +1522,11 @@
         <artifactId>camel-oaipmh</artifactId>
         <version>4.11.0-SNAPSHOT</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>camel-oauth</artifactId>
+        <version>4.11.0-SNAPSHOT</version>
+      </dependency>
       <dependency>
         <groupId>org.apache.camel</groupId>
         <artifactId>camel-observability-services</artifactId>
diff --git a/catalog/camel-allcomponents/pom.xml b/catalog/camel-allcomponents/pom.xml
index 7a95d3a5766..650ab279f76 100644
--- a/catalog/camel-allcomponents/pom.xml
+++ b/catalog/camel-allcomponents/pom.xml
@@ -1341,6 +1341,11 @@
             <artifactId>camel-oaipmh</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-oauth</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-observability-services</artifactId>
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
index dca62a83195..6b15779ac90 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
@@ -29,6 +29,7 @@ micrometer-prometheus
 microprofile-config
 microprofile-fault-tolerance
 microprofile-health
+oauth
 observability-services
 observation
 openapi-java
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/oauth.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/oauth.json
new file mode 100644
index 00000000000..2e0a95e0551
--- /dev/null
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/oauth.json
@@ -0,0 +1,13 @@
+{
+  "other": {
+    "kind": "other",
+    "name": "oauth",
+    "title": "Oauth",
+    "description": "Camel OAuth (Preview)",
+    "deprecated": false,
+    "supportLevel": "Preview",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-oauth",
+    "version": "4.11.0-SNAPSHOT"
+  }
+}
diff --git a/components/camel-oauth/README.md b/components/camel-oauth/README.md
new file mode 100644
index 00000000000..73e5b09951c
--- /dev/null
+++ b/components/camel-oauth/README.md
@@ -0,0 +1,101 @@
+
+# Camel OAuth (Preview)
+
+The camel-oauth module comes with a set of Processors that can be added to a route on the client and resource owner side
+like [this|https://github.com/tdiesler/camel-cloud-examples/blob/main/camel-main/platform-http-oauth/platform-http-files/platform-http-route.yaml] ...
+
+## Supported OIDC, OAuth functionality
+
+* OIDC Authentication using Authorization Code Flow (OAuthCodeFlowProcessor, OAuthCodeFlowCallbackProcessor)
+* OAuth Client Credentials Grant (OAuthClientCredentialsProcessor, OAuthBearerTokenProcessor)
+* Identity Provider (Keycloak only) logout (OAuthLogoutProcessor)
+
+For details, see these specs ...
+
+* [OAuth 2.0|https://datatracker.ietf.org/doc/html/rfc6749]
+* [OIDC 1.0|https://openid.net/specs/openid-connect-core-1_0.html]
+
+Our Identity Provider (Keycloak) can be deployed to a local k8s cluster with a [helm chart|./helm] that comes with this project.
+
+Respective jbang projects live [here|https://github.com/tdiesler/camel-cloud-examples/tree/main/camel-main]
+
+For Kafka we use strimzi kafka-oauth-client directly, which is documented [here|https://github.com/tdiesler/camel-cloud-examples/blob/main/camel-main/kafka-oauth/kafka-oauth-route.yaml].
+
+## Keycloak as the Identity Provider
+
+Currently, we use Keycloak as OIDC Provider - Hashicorp Vault is underway. 
+
+### Keycloak Certificate
+
+```
+# Generate TLS Certificate
+openssl req -x509 -newkey rsa:4096 -keyout ./helm/etc/keycloak.key -out ./helm/etc/keycloak.crt -days 365 -nodes -config ./helm/etc/san.cnf
+
+# Import TLS Certificate to Java Keystore (i.e. trust the certificate)
+sudo keytool -import -alias keycloak -file ./helm/etc/keycloak.crt -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit
+
+# Remove TLS Certificate from Java Keystore
+sudo keytool -delete -alias keycloak -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit
+```
+
+### Keycloak on Kubernetes
+
+Deploy Keycloak as Identity Provider
+
+Admin:  admin/admin
+User:   alice/alice
+
+https://keycloak.local:30443/
+
+Host 'keycloak.local' should be mapped (/etc/hosts) to your local k8s IP
+
+```
+kubectl config use-context docker-desktop \
+    && helm upgrade --install keycloak ./helm -f ./helm/values-keycloak.yaml \
+    && kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=keycloak --timeout=20s \
+    && kubectl logs --tail 400 -f -l app.kubernetes.io/name=keycloak
+
+helm uninstall keycloak
+```
+
+### Keycloak Admin Tasks
+
+Run this when realm 'camel' if not already imported
+
+```
+kcadm config credentials --server https://keycloak.local:30443 --realm master --user admin --password admin
+
+kcadm create realms -s realm=camel -s enabled=true
+
+kcadm create clients -r camel \
+    -s clientId=camel-client \
+    -s publicClient=false \
+    -s standardFlowEnabled=true \
+    -s serviceAccountsEnabled=true \
+    -s "redirectUris=[\"http://127.0.0.1:8080/auth\"]" \
+    -s "attributes.\"post.logout.redirect.uris\"=\"http://127.0.0.1:8080/\""
+    
+clientId=$(kcadm get clients -r camel -q clientId=camel-client --fields id --format csv --noquotes)
+kcadm update clients/${clientId} -r camel -s secret="camel-client-secret"
+
+kcadm create users -r camel \
+    -s username=alice \
+    -s email=alice@example.com \
+    -s emailVerified=true \
+    -s firstName=Alice \
+    -s lastName=Brown \
+    -s enabled=true
+    
+userid=$(kcadm get users -r camel -q username=alice --fields id --format csv --noquotes)
+kcadm set-password -r camel --userid=${userid} --new-password alice    
+
+kcadm delete realms/camel -r master
+```
+
+Show client/user configuration
+
+```
+kcadm get clients -r camel | jq -r '.[] | select(.clientId=="camel-client")'
+
+kcadm get users -r camel | jq -r '.[] | select(.username=="alice")'
+```
diff --git a/components/camel-oauth/helm/Chart.yaml b/components/camel-oauth/helm/Chart.yaml
new file mode 100644
index 00000000000..e455139ac69
--- /dev/null
+++ b/components/camel-oauth/helm/Chart.yaml
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+apiVersion: v2
+name: platform-http-chart
+description: A Helm chart to deploy Keycloak
+version: 0.1.0
\ No newline at end of file
diff --git a/components/camel-oauth/helm/etc/camel-realm.json b/components/camel-oauth/helm/etc/camel-realm.json
new file mode 100644
index 00000000000..899d037f676
--- /dev/null
+++ b/components/camel-oauth/helm/etc/camel-realm.json
@@ -0,0 +1,2139 @@
+{
+  "accessCodeLifespan": 60,
+  "accessCodeLifespanLogin": 1800,
+  "accessCodeLifespanUserAction": 300,
+  "accessTokenLifespan": 300,
+  "accessTokenLifespanForImplicitFlow": 900,
+  "actionTokenGeneratedByAdminLifespan": 43200,
+  "actionTokenGeneratedByUserLifespan": 300,
+  "adminEventsDetailsEnabled": false,
+  "adminEventsEnabled": false,
+  "adminPermissionsEnabled": false,
+  "attributes": {
+    "cibaAuthRequestedUserHint": "login_hint",
+    "cibaBackchannelTokenDeliveryMode": "poll",
+    "cibaExpiresIn": "120",
+    "cibaInterval": "5",
+    "oauth2DeviceCodeLifespan": "600",
+    "oauth2DevicePollingInterval": "5",
+    "parRequestUriLifespan": "60",
+    "realmReusableOtpCode": "false"
+  },
+  "authenticationFlows": [
+    {
+      "alias": "Account verification options",
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-email-verification",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "Verify Existing Account by Re-authentication",
+          "priority": 20,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Method with which to verity the existing account",
+      "id": "cae070ea-ddce-46fb-be70-795d59869217",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "Browser - Conditional OTP",
+      "authenticationExecutions": [
+        {
+          "authenticator": "conditional-user-configured",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "auth-otp-form",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Flow to determine if the OTP is required for the authentication",
+      "id": "87907cb4-8369-4f18-9daa-6c834726644b",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "Browser - Conditional Organization",
+      "authenticationExecutions": [
+        {
+          "authenticator": "conditional-user-configured",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "organization",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Flow to determine if the organization identity-first login is to be used",
+      "id": "37d88d49-79c8-4d3c-8546-bfe103085117",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "Direct Grant - Conditional OTP",
+      "authenticationExecutions": [
+        {
+          "authenticator": "conditional-user-configured",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "direct-grant-validate-otp",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Flow to determine if the OTP is required for the authentication",
+      "id": "767b935b-6e00-4884-a9f0-90281cf54634",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "First Broker Login - Conditional Organization",
+      "authenticationExecutions": [
+        {
+          "authenticator": "conditional-user-configured",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "idp-add-organization-member",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Flow to determine if the authenticator that adds organization members is to be used",
+      "id": "a1182ccd-bda9-40ac-a28f-da1b53c62191",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "First broker login - Conditional OTP",
+      "authenticationExecutions": [
+        {
+          "authenticator": "conditional-user-configured",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "auth-otp-form",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Flow to determine if the OTP is required for the authentication",
+      "id": "39020d63-f50b-4bab-824a-732d274324a6",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "Handle Existing Account",
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-confirm-link",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "Account verification options",
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+      "id": "1be565ad-e41c-412e-86ff-aa5fff4183c5",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "Organization",
+      "authenticationExecutions": [
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "Browser - Conditional Organization",
+          "priority": 10,
+          "requirement": "CONDITIONAL",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "id": "a4b5f3f3-cbae-4b14-99a0-d9b3b667543a",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "Reset - Conditional OTP",
+      "authenticationExecutions": [
+        {
+          "authenticator": "conditional-user-configured",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "reset-otp",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
+      "id": "b49293c6-ded0-4edc-9a5f-d2c1ae5a3d56",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "User creation or linking",
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-create-user-if-unique",
+          "authenticatorConfig": "create unique user config",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "Handle Existing Account",
+          "priority": 20,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Flow for the existing/non-existing user alternatives",
+      "id": "1cb7464a-d7c0-459e-b09c-2847984109da",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "Verify Existing Account by Re-authentication",
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-username-password-form",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "First broker login - Conditional OTP",
+          "priority": 20,
+          "requirement": "CONDITIONAL",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Reauthentication of existing account",
+      "id": "6c7a7a86-da4f-4bea-a59b-6ab3f4e5af66",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "browser",
+      "authenticationExecutions": [
+        {
+          "authenticator": "auth-cookie",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "auth-spnego",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "DISABLED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "identity-provider-redirector",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 25,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "Organization",
+          "priority": 26,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "forms",
+          "priority": 30,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Browser based authentication",
+      "id": "d52b496a-d02f-4f43-bea1-db5bb3fd935e",
+      "providerId": "basic-flow",
+      "topLevel": true
+    },
+    {
+      "alias": "clients",
+      "authenticationExecutions": [
+        {
+          "authenticator": "client-secret",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "client-jwt",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "client-secret-jwt",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 30,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "client-x509",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 40,
+          "requirement": "ALTERNATIVE",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Base authentication for clients",
+      "id": "e10bdc41-0c9c-4e66-b7e1-53bedaee3bc2",
+      "providerId": "client-flow",
+      "topLevel": true
+    },
+    {
+      "alias": "direct grant",
+      "authenticationExecutions": [
+        {
+          "authenticator": "direct-grant-validate-username",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "direct-grant-validate-password",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "Direct Grant - Conditional OTP",
+          "priority": 30,
+          "requirement": "CONDITIONAL",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "OpenID Connect Resource Owner Grant",
+      "id": "584d7f63-e86d-44e9-ba87-41bc2303f14a",
+      "providerId": "basic-flow",
+      "topLevel": true
+    },
+    {
+      "alias": "docker auth",
+      "authenticationExecutions": [
+        {
+          "authenticator": "docker-http-basic-authenticator",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Used by Docker clients to authenticate against the IDP",
+      "id": "993a5f27-ecff-4c87-a6fe-89e557484dac",
+      "providerId": "basic-flow",
+      "topLevel": true
+    },
+    {
+      "alias": "first broker login",
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-review-profile",
+          "authenticatorConfig": "review profile config",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "User creation or linking",
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "First Broker Login - Conditional Organization",
+          "priority": 50,
+          "requirement": "CONDITIONAL",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+      "id": "e5c203ab-87bd-4b3a-ac4c-ffe02d0807fb",
+      "providerId": "basic-flow",
+      "topLevel": true
+    },
+    {
+      "alias": "forms",
+      "authenticationExecutions": [
+        {
+          "authenticator": "auth-username-password-form",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "Browser - Conditional OTP",
+          "priority": 20,
+          "requirement": "CONDITIONAL",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Username, password, otp and other auth forms.",
+      "id": "dc505ff6-a644-4c67-99a7-f80109adc7bb",
+      "providerId": "basic-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "registration",
+      "authenticationExecutions": [
+        {
+          "authenticator": "registration-page-form",
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "registration form",
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Registration flow",
+      "id": "e0eace41-ef5e-4039-804d-78be3279f900",
+      "providerId": "basic-flow",
+      "topLevel": true
+    },
+    {
+      "alias": "registration form",
+      "authenticationExecutions": [
+        {
+          "authenticator": "registration-user-creation",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "registration-password-action",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 50,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "registration-recaptcha-action",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 60,
+          "requirement": "DISABLED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "registration-terms-and-conditions",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 70,
+          "requirement": "DISABLED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Registration form",
+      "id": "da98451c-b801-4d74-bd38-af81169e1fa9",
+      "providerId": "form-flow",
+      "topLevel": false
+    },
+    {
+      "alias": "reset credentials",
+      "authenticationExecutions": [
+        {
+          "authenticator": "reset-credentials-choose-user",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "reset-credential-email",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 20,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticator": "reset-password",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 30,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        },
+        {
+          "authenticatorFlow": true,
+          "autheticatorFlow": true,
+          "flowAlias": "Reset - Conditional OTP",
+          "priority": 40,
+          "requirement": "CONDITIONAL",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "Reset credentials for a user if they forgot their password or something",
+      "id": "baf2e059-e2ed-4511-8cb9-9bae87ed2079",
+      "providerId": "basic-flow",
+      "topLevel": true
+    },
+    {
+      "alias": "saml ecp",
+      "authenticationExecutions": [
+        {
+          "authenticator": "http-basic-authenticator",
+          "authenticatorFlow": false,
+          "autheticatorFlow": false,
+          "priority": 10,
+          "requirement": "REQUIRED",
+          "userSetupAllowed": false
+        }
+      ],
+      "builtIn": true,
+      "description": "SAML ECP Profile Authentication Flow",
+      "id": "40a31665-a2cd-48cc-b3fb-572e045b9fc5",
+      "providerId": "basic-flow",
+      "topLevel": true
+    }
+  ],
+  "authenticatorConfig": [
+    {
+      "alias": "create unique user config",
+      "config": {
+        "require.password.update.after.registration": "false"
+      },
+      "id": "3ab5456f-5c1c-445d-a772-a986b59b36a9"
+    },
+    {
+      "alias": "review profile config",
+      "config": {
+        "update.profile.on.first.login": "missing"
+      },
+      "id": "50608b3e-9f44-41f0-b535-5ef3979167e6"
+    }
+  ],
+  "browserFlow": "browser",
+  "browserSecurityHeaders": {
+    "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+    "contentSecurityPolicyReportOnly": "",
+    "referrerPolicy": "no-referrer",
+    "strictTransportSecurity": "max-age=31536000; includeSubDomains",
+    "xContentTypeOptions": "nosniff",
+    "xFrameOptions": "SAMEORIGIN",
+    "xRobotsTag": "none",
+    "xXSSProtection": "1; mode=block"
+  },
+  "bruteForceProtected": false,
+  "bruteForceStrategy": "MULTIPLE",
+  "clientAuthenticationFlow": "clients",
+  "clientOfflineSessionIdleTimeout": 0,
+  "clientOfflineSessionMaxLifespan": 0,
+  "clientPolicies": {
+    "policies": []
+  },
+  "clientProfiles": {
+    "profiles": []
+  },
+  "clientScopeMappings": {
+    "account": [
+      {
+        "client": "account-console",
+        "roles": [
+          "manage-account",
+          "view-groups"
+        ]
+      }
+    ]
+  },
+  "clientScopes": [
+    {
+      "attributes": {
+        "display.on.consent.screen": "false",
+        "include.in.token.scope": "true"
+      },
+      "description": "Microprofile - JWT built-in scope",
+      "id": "7aaaf3c4-868f-41d2-868d-35eb057cd205",
+      "name": "microprofile-jwt",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "upn",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "username",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "21cff289-9683-4e5b-a2e1-a6b83c24975a",
+          "name": "upn",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "groups",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "multivalued": "true",
+            "user.attribute": "foo"
+          },
+          "consentRequired": false,
+          "id": "52c78de5-cfd1-4b44-96d2-522bd0d0f338",
+          "name": "groups",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-realm-role-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "consent.screen.text": "${addressScopeConsentText}",
+        "display.on.consent.screen": "true",
+        "include.in.token.scope": "true"
+      },
+      "description": "OpenID Connect built-in scope: address",
+      "id": "344e25fa-1a59-439a-87a3-9b67b3b939a4",
+      "name": "address",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "user.attribute.country": "country",
+            "user.attribute.formatted": "formatted",
+            "user.attribute.locality": "locality",
+            "user.attribute.postal_code": "postal_code",
+            "user.attribute.region": "region",
+            "user.attribute.street": "street",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "d651d31e-11c1-428f-b07a-4e60ed0a9af5",
+          "name": "address",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-address-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "consent.screen.text": "${phoneScopeConsentText}",
+        "display.on.consent.screen": "true",
+        "include.in.token.scope": "true"
+      },
+      "description": "OpenID Connect built-in scope: phone",
+      "id": "f7e20abd-09bf-4d24-825a-58780fb6b29b",
+      "name": "phone",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "phone_number",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "phoneNumber",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "425fcb87-d8f4-4021-a198-71af50f6b5ed",
+          "name": "phone number",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "phone_number_verified",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "boolean",
+            "user.attribute": "phoneNumberVerified",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "54b3ff41-3bde-4b31-aeb9-f17f1a831b4f",
+          "name": "phone number verified",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "display.on.consent.screen": "false",
+        "include.in.token.scope": "false"
+      },
+      "description": "Specific scope for a client enabled for service accounts",
+      "id": "4435f35a-8bd4-48b2-81a6-28b938a175da",
+      "name": "service_account",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "clientAddress",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.session.note": "clientAddress"
+          },
+          "consentRequired": false,
+          "id": "4f2bd9a5-6885-47ae-8bb5-957e6c95eca8",
+          "name": "Client IP Address",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usersessionmodel-note-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "client_id",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.session.note": "client_id"
+          },
+          "consentRequired": false,
+          "id": "4d23d59a-16c1-46b0-9ee0-afbcfeaea959",
+          "name": "Client ID",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usersessionmodel-note-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "clientHost",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.session.note": "clientHost"
+          },
+          "consentRequired": false,
+          "id": "91802fe0-be3b-4960-99d3-c3c52dc0c885",
+          "name": "Client Host",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usersessionmodel-note-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "consent.screen.text": "${profileScopeConsentText}",
+        "display.on.consent.screen": "true",
+        "include.in.token.scope": "true"
+      },
+      "description": "OpenID Connect built-in scope: profile",
+      "id": "54fa7985-ca72-45b9-aeab-c31fc11a3455",
+      "name": "profile",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "profile",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "profile",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "f2d2e2dd-8f19-4730-82fe-d4359f7f6f1b",
+          "name": "profile",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "picture",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "picture",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "ebe75f43-8f40-4ca8-b62f-345d36f61a8c",
+          "name": "picture",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "gender",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "gender",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "846a582e-86cc-4c9c-9f65-269fed23f6a2",
+          "name": "gender",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "zoneinfo",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "zoneinfo",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "04d178f2-c8fa-45d6-b0be-c8acd2e16b3c",
+          "name": "zoneinfo",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "locale",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "locale",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "e8b997dc-4684-4758-9846-480b6e4e9ca4",
+          "name": "locale",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "updated_at",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "long",
+            "user.attribute": "updatedAt",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "4940a100-c258-49a6-a0fe-7c946b1c4ce3",
+          "name": "updated at",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "preferred_username",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "username",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "d4a5f0ca-7ffd-4706-ae4b-d8ca928ee599",
+          "name": "username",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "7936f5b4-b5e5-4400-b6e0-ed0f23eb43d3",
+          "name": "full name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-full-name-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "family_name",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "lastName",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "f91eeb97-47d3-4b4a-a03e-9610dea8ad26",
+          "name": "family name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "birthdate",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "birthdate",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "388281ea-5d95-4670-b2a3-5230104511a6",
+          "name": "birthdate",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "middle_name",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "middleName",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "57d612d3-698d-47fd-bf7d-9663276c3088",
+          "name": "middle name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "given_name",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "firstName",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "253ed51c-994f-4b64-8c24-bc423925a845",
+          "name": "given name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "website",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "website",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "1c152cff-f9c3-4fcd-9394-1c09c4a77e75",
+          "name": "website",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "nickname",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "nickname",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "5c172e81-a500-46fe-be77-6fb6e4e3aedc",
+          "name": "nickname",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "consent.screen.text": "${emailScopeConsentText}",
+        "display.on.consent.screen": "true",
+        "include.in.token.scope": "true"
+      },
+      "description": "OpenID Connect built-in scope: email",
+      "id": "ea20e4dc-8701-4dc5-b4d4-a2a075419685",
+      "name": "email",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "email_verified",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "boolean",
+            "user.attribute": "emailVerified",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "57060a43-fe4f-4ed6-80d2-8f500a2ac6a9",
+          "name": "email verified",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "email",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "email",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "e37af486-8e38-4df8-863d-72f954e109f6",
+          "name": "email",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "display.on.consent.screen": "false",
+        "include.in.token.scope": "false"
+      },
+      "description": "OpenID Connect scope for add acr (authentication context class reference) to the token",
+      "id": "eade01bc-3b70-451e-a3e9-a2d2d98d6015",
+      "name": "acr",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "35cda456-a597-4f96-85e7-991993784768",
+          "name": "acr loa level",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-acr-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "display.on.consent.screen": "false"
+      },
+      "description": "Organization Membership",
+      "id": "4a8b5d37-212e-49dc-8f4a-a611af9ea0e0",
+      "name": "saml_organization",
+      "protocol": "saml",
+      "protocolMappers": [
+        {
+          "config": {},
+          "consentRequired": false,
+          "id": "ffdc0525-b40e-4f9b-98d9-92c67845176d",
+          "name": "organization",
+          "protocol": "saml",
+          "protocolMapper": "saml-organization-membership-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "consent.screen.text": "${samlRoleListScopeConsentText}",
+        "display.on.consent.screen": "true"
+      },
+      "description": "SAML role list",
+      "id": "8ca5dd23-6618-4e3d-aaa2-53c4075f3807",
+      "name": "role_list",
+      "protocol": "saml",
+      "protocolMappers": [
+        {
+          "config": {
+            "attribute.name": "Role",
+            "attribute.nameformat": "Basic",
+            "single": "false"
+          },
+          "consentRequired": false,
+          "id": "8ffb7639-1cb1-4d69-9a31-7804b8e483ea",
+          "name": "role list",
+          "protocol": "saml",
+          "protocolMapper": "saml-role-list-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "consent.screen.text": "${organizationScopeConsentText}",
+        "display.on.consent.screen": "true",
+        "include.in.token.scope": "true"
+      },
+      "description": "Additional claims about the organization a subject belongs to",
+      "id": "20664e6a-1dd1-4bac-9680-dbae402cf691",
+      "name": "organization",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "organization",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "multivalued": "true"
+          },
+          "consentRequired": false,
+          "id": "6c462bdf-90c5-44f5-94d2-0002cc167b46",
+          "name": "organization",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-organization-membership-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "consent.screen.text": "${offlineAccessScopeConsentText}",
+        "display.on.consent.screen": "true"
+      },
+      "description": "OpenID Connect built-in scope: offline_access",
+      "id": "ed38bfbc-8f29-4f64-935b-5cf06d2215f7",
+      "name": "offline_access",
+      "protocol": "openid-connect"
+    },
+    {
+      "attributes": {
+        "consent.screen.text": "${rolesScopeConsentText}",
+        "display.on.consent.screen": "true",
+        "include.in.token.scope": "false"
+      },
+      "description": "OpenID Connect scope for add user roles to the access token",
+      "id": "fb84a8f0-1a0b-4fa6-887f-8c2800c6fbd9",
+      "name": "roles",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "realm_access.roles",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "multivalued": "true",
+            "user.attribute": "foo"
+          },
+          "consentRequired": false,
+          "id": "03ad1d22-79ae-4432-b175-2c1191529b09",
+          "name": "realm roles",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-realm-role-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "resource_access.${client_id}.roles",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "multivalued": "true",
+            "user.attribute": "foo"
+          },
+          "consentRequired": false,
+          "id": "d48fa36e-fd7d-41aa-9c89-14a2b3cfc5b1",
+          "name": "client roles",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-client-role-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "introspection.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "fdb7d9ca-93db-40cd-a189-299e2dcc4483",
+          "name": "audience resolve",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-audience-resolve-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "consent.screen.text": "",
+        "display.on.consent.screen": "false",
+        "include.in.token.scope": "false"
+      },
+      "description": "OpenID Connect scope for add allowed web origins to the access token",
+      "id": "3ee41590-b580-4114-a4f9-ae7311699adb",
+      "name": "web-origins",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "introspection.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "ee56b7f1-a844-48c3-a55e-374b57fe6ca5",
+          "name": "allowed web origins",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-allowed-origins-mapper"
+        }
+      ]
+    },
+    {
+      "attributes": {
+        "display.on.consent.screen": "false",
+        "include.in.token.scope": "false"
+      },
+      "description": "OpenID Connect scope for add all basic claims to the token",
+      "id": "7eacd797-dd65-412a-9fa5-8ba3d4824e64",
+      "name": "basic",
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "introspection.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "260dd52c-632b-46ef-8210-a3d8351d8523",
+          "name": "sub",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-sub-mapper"
+        },
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "auth_time",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "long",
+            "user.session.note": "AUTH_TIME"
+          },
+          "consentRequired": false,
+          "id": "8e1d4215-9034-46c1-ab66-2c756840452e",
+          "name": "auth_time",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usersessionmodel-note-mapper"
+        }
+      ]
+    }
+  ],
+  "clientSessionIdleTimeout": 0,
+  "clientSessionMaxLifespan": 0,
+  "clients": [
+    {
+      "alwaysDisplayInConsole": false,
+      "attributes": {
+        "post.logout.redirect.uris": "+",
+        "realm_client": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "baseUrl": "/realms/camel/account/",
+      "bearerOnly": false,
+      "clientAuthenticatorType": "client-secret",
+      "clientId": "account",
+      "consentRequired": false,
+      "defaultClientScopes": [
+        "web-origins",
+        "acr",
+        "roles",
+        "profile",
+        "basic",
+        "email"
+      ],
+      "directAccessGrantsEnabled": false,
+      "enabled": true,
+      "frontchannelLogout": false,
+      "fullScopeAllowed": false,
+      "id": "f5aaf582-f7c0-4c6c-bcf2-390660090115",
+      "implicitFlowEnabled": false,
+      "name": "${client_account}",
+      "nodeReRegistrationTimeout": 0,
+      "notBefore": 0,
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "organization",
+        "offline_access",
+        "microprofile-jwt"
+      ],
+      "protocol": "openid-connect",
+      "publicClient": true,
+      "redirectUris": [
+        "/realms/camel/account/*"
+      ],
+      "rootUrl": "${authBaseUrl}",
+      "serviceAccountsEnabled": false,
+      "standardFlowEnabled": true,
+      "surrogateAuthRequired": false,
+      "webOrigins": []
+    },
+    {
+      "alwaysDisplayInConsole": false,
+      "attributes": {
+        "pkce.code.challenge.method": "S256",
+        "post.logout.redirect.uris": "+",
+        "realm_client": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "baseUrl": "/realms/camel/account/",
+      "bearerOnly": false,
+      "clientAuthenticatorType": "client-secret",
+      "clientId": "account-console",
+      "consentRequired": false,
+      "defaultClientScopes": [
+        "web-origins",
+        "acr",
+        "roles",
+        "profile",
+        "basic",
+        "email"
+      ],
+      "directAccessGrantsEnabled": false,
+      "enabled": true,
+      "frontchannelLogout": false,
+      "fullScopeAllowed": false,
+      "id": "ece35b24-8c9e-4970-9c05-a4dc38d382a7",
+      "implicitFlowEnabled": false,
+      "name": "${client_account-console}",
+      "nodeReRegistrationTimeout": 0,
+      "notBefore": 0,
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "organization",
+        "offline_access",
+        "microprofile-jwt"
+      ],
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {},
+          "consentRequired": false,
+          "id": "5acc723e-c018-490d-8b7a-e31c23e673db",
+          "name": "audience resolve",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-audience-resolve-mapper"
+        }
+      ],
+      "publicClient": true,
+      "redirectUris": [
+        "/realms/camel/account/*"
+      ],
+      "rootUrl": "${authBaseUrl}",
+      "serviceAccountsEnabled": false,
+      "standardFlowEnabled": true,
+      "surrogateAuthRequired": false,
+      "webOrigins": []
+    },
+    {
+      "alwaysDisplayInConsole": false,
+      "attributes": {
+        "client.use.lightweight.access.token.enabled": "true",
+        "realm_client": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "bearerOnly": false,
+      "clientAuthenticatorType": "client-secret",
+      "clientId": "admin-cli",
+      "consentRequired": false,
+      "defaultClientScopes": [
+        "web-origins",
+        "acr",
+        "roles",
+        "profile",
+        "basic",
+        "email"
+      ],
+      "directAccessGrantsEnabled": true,
+      "enabled": true,
+      "frontchannelLogout": false,
+      "fullScopeAllowed": true,
+      "id": "b172229f-58b9-4e19-9f9d-ee91d65fd67e",
+      "implicitFlowEnabled": false,
+      "name": "${client_admin-cli}",
+      "nodeReRegistrationTimeout": 0,
+      "notBefore": 0,
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "organization",
+        "offline_access",
+        "microprofile-jwt"
+      ],
+      "protocol": "openid-connect",
+      "publicClient": true,
+      "redirectUris": [],
+      "serviceAccountsEnabled": false,
+      "standardFlowEnabled": false,
+      "surrogateAuthRequired": false,
+      "webOrigins": []
+    },
+    {
+      "alwaysDisplayInConsole": false,
+      "attributes": {
+        "realm_client": "true"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "bearerOnly": true,
+      "clientAuthenticatorType": "client-secret",
+      "clientId": "broker",
+      "consentRequired": false,
+      "defaultClientScopes": [
+        "web-origins",
+        "acr",
+        "roles",
+        "profile",
+        "basic",
+        "email"
+      ],
+      "directAccessGrantsEnabled": false,
+      "enabled": true,
+      "frontchannelLogout": false,
+      "fullScopeAllowed": false,
+      "id": "62567bd0-4265-4ae3-a802-6cc2d944a5b7",
+      "implicitFlowEnabled": false,
+      "name": "${client_broker}",
+      "nodeReRegistrationTimeout": 0,
+      "notBefore": 0,
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "organization",
+        "offline_access",
+        "microprofile-jwt"
+      ],
+      "protocol": "openid-connect",
+      "publicClient": false,
+      "redirectUris": [],
+      "serviceAccountsEnabled": false,
+      "standardFlowEnabled": true,
+      "surrogateAuthRequired": false,
+      "webOrigins": []
+    },
+    {
+      "alwaysDisplayInConsole": false,
+      "attributes": {
+        "client.secret.creation.time": "1739866545",
+        "post.logout.redirect.uris": "http://127.0.0.1:8080/",
+        "realm_client": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "bearerOnly": false,
+      "clientAuthenticatorType": "client-secret",
+      "clientId": "camel-client",
+      "consentRequired": false,
+      "defaultClientScopes": [
+        "service_account",
+        "web-origins",
+        "acr",
+        "roles",
+        "profile",
+        "basic",
+        "email"
+      ],
+      "directAccessGrantsEnabled": false,
+      "enabled": true,
+      "frontchannelLogout": false,
+      "fullScopeAllowed": true,
+      "id": "528c7a81-f7e2-40d9-9745-5af73e16e2d6",
+      "implicitFlowEnabled": false,
+      "nodeReRegistrationTimeout": -1,
+      "notBefore": 0,
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "organization",
+        "offline_access",
+        "microprofile-jwt"
+      ],
+      "protocol": "openid-connect",
+      "publicClient": false,
+      "redirectUris": [
+        "http://127.0.0.1:8080/auth"
+      ],
+      "secret": "camel-client-secret",
+      "serviceAccountsEnabled": true,
+      "standardFlowEnabled": true,
+      "surrogateAuthRequired": false,
+      "webOrigins": [
+        "http://127.0.0.1:8080"
+      ]
+    },
+    {
+      "alwaysDisplayInConsole": false,
+      "attributes": {
+        "realm_client": "true"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "bearerOnly": true,
+      "clientAuthenticatorType": "client-secret",
+      "clientId": "realm-management",
+      "consentRequired": false,
+      "defaultClientScopes": [
+        "web-origins",
+        "acr",
+        "roles",
+        "profile",
+        "basic",
+        "email"
+      ],
+      "directAccessGrantsEnabled": false,
+      "enabled": true,
+      "frontchannelLogout": false,
+      "fullScopeAllowed": false,
+      "id": "b517865c-a141-4ff2-8462-d7b55e040dab",
+      "implicitFlowEnabled": false,
+      "name": "${client_realm-management}",
+      "nodeReRegistrationTimeout": 0,
+      "notBefore": 0,
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "organization",
+        "offline_access",
+        "microprofile-jwt"
+      ],
+      "protocol": "openid-connect",
+      "publicClient": false,
+      "redirectUris": [],
+      "serviceAccountsEnabled": false,
+      "standardFlowEnabled": true,
+      "surrogateAuthRequired": false,
+      "webOrigins": []
+    },
+    {
+      "alwaysDisplayInConsole": false,
+      "attributes": {
+        "client.use.lightweight.access.token.enabled": "true",
+        "pkce.code.challenge.method": "S256",
+        "post.logout.redirect.uris": "+",
+        "realm_client": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "baseUrl": "/admin/camel/console/",
+      "bearerOnly": false,
+      "clientAuthenticatorType": "client-secret",
+      "clientId": "security-admin-console",
+      "consentRequired": false,
+      "defaultClientScopes": [
+        "web-origins",
+        "acr",
+        "roles",
+        "profile",
+        "basic",
+        "email"
+      ],
+      "directAccessGrantsEnabled": false,
+      "enabled": true,
+      "frontchannelLogout": false,
+      "fullScopeAllowed": true,
+      "id": "e92f2b26-aa13-46fe-84e8-3d0e86bed8d4",
+      "implicitFlowEnabled": false,
+      "name": "${client_security-admin-console}",
+      "nodeReRegistrationTimeout": 0,
+      "notBefore": 0,
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "organization",
+        "offline_access",
+        "microprofile-jwt"
+      ],
+      "protocol": "openid-connect",
+      "protocolMappers": [
+        {
+          "config": {
+            "access.token.claim": "true",
+            "claim.name": "locale",
+            "id.token.claim": "true",
+            "introspection.token.claim": "true",
+            "jsonType.label": "String",
+            "user.attribute": "locale",
+            "userinfo.token.claim": "true"
+          },
+          "consentRequired": false,
+          "id": "17841641-7b03-43c8-8307-596bd2572ffc",
+          "name": "locale",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper"
+        }
+      ],
+      "publicClient": true,
+      "redirectUris": [
+        "/admin/camel/console/*"
+      ],
+      "rootUrl": "${authAdminUrl}",
+      "serviceAccountsEnabled": false,
+      "standardFlowEnabled": true,
+      "surrogateAuthRequired": false,
+      "webOrigins": [
+        "+"
+      ]
+    }
+  ],
+  "components": {
+    "org.keycloak.keys.KeyProvider": [
+      {
+        "config": {
+          "priority": [
+            "100"
+          ]
+        },
+        "id": "0ebda9b5-8bc8-4d8e-897f-b1dbb6b37a92",
+        "name": "rsa-generated",
+        "providerId": "rsa-generated",
+        "subComponents": {}
+      },
+      {
+        "config": {
+          "active": [
+            "true"
+          ],
+          "algorithm": [
+            "RSA-OAEP"
+          ],
+          "enabled": [
+            "false"
+          ],
+          "keySize": [
+            "2048"
+          ],
+          "priority": [
+            "100"
+          ]
+        },
+        "id": "f52a4515-1e39-4cb4-b51f-4430568e2b90",
+        "name": "rsa-enc-generated",
+        "providerId": "rsa-enc-generated",
+        "subComponents": {}
+      },
+      {
+        "config": {
+          "priority": [
+            "100"
+          ]
+        },
+        "id": "2163323c-747b-4ad6-85a2-4e6307fb30fd",
+        "name": "aes-generated",
+        "providerId": "aes-generated",
+        "subComponents": {}
+      },
+      {
+        "config": {
+          "algorithm": [
+            "HS512"
+          ],
+          "priority": [
+            "100"
+          ]
+        },
+        "id": "bec37f80-4e3f-4469-81c6-08f27ee41f28",
+        "name": "hmac-generated-hs512",
+        "providerId": "hmac-generated",
+        "subComponents": {}
+      }
+    ],
+    "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+      {
+        "config": {
+          "max-clients": [
+            "200"
+          ]
+        },
+        "id": "7774d67c-b440-4477-9ee7-8d1d5239fd80",
+        "name": "Max Clients Limit",
+        "providerId": "max-clients",
+        "subComponents": {},
+        "subType": "anonymous"
+      },
+      {
+        "config": {
+          "client-uris-must-match": [
+            "true"
+          ],
+          "host-sending-registration-request-must-match": [
+            "true"
+          ]
+        },
+        "id": "d78cce55-836a-4c91-8e7c-6233b70b4ed0",
+        "name": "Trusted Hosts",
+        "providerId": "trusted-hosts",
+        "subComponents": {},
+        "subType": "anonymous"
+      },
+      {
+        "config": {},
+        "id": "bb69f779-b16f-40fc-925b-3c670233a29b",
+        "name": "Full Scope Disabled",
+        "providerId": "scope",
+        "subComponents": {},
+        "subType": "anonymous"
+      },
+      {
+        "config": {
+          "allow-default-scopes": [
+            "true"
+          ]
+        },
+        "id": "32e6dcdd-874f-4c9d-b31c-2fd869900b6c",
+        "name": "Allowed Client Scopes",
+        "providerId": "allowed-client-templates",
+        "subComponents": {},
+        "subType": "authenticated"
+      },
+      {
+        "config": {},
+        "id": "ab34db59-db6f-4037-b896-c98ad1b6a87a",
+        "name": "Consent Required",
+        "providerId": "consent-required",
+        "subComponents": {},
+        "subType": "anonymous"
+      },
+      {
+        "config": {
+          "allowed-protocol-mapper-types": [
+            "saml-user-property-mapper",
+            "saml-role-list-mapper",
+            "oidc-usermodel-attribute-mapper",
+            "oidc-address-mapper",
+            "saml-user-attribute-mapper",
+            "oidc-usermodel-property-mapper",
+            "oidc-full-name-mapper",
+            "oidc-sha256-pairwise-sub-mapper"
+          ]
+        },
+        "id": "d7fa36e0-89ac-4b1a-8b1f-4cb32af415d8",
+        "name": "Allowed Protocol Mapper Types",
+        "providerId": "allowed-protocol-mappers",
+        "subComponents": {},
+        "subType": "authenticated"
+      },
+      {
+        "config": {
+          "allow-default-scopes": [
+            "true"
+          ]
+        },
+        "id": "a12a2c6c-531c-4c3b-9254-43fba109cab3",
+        "name": "Allowed Client Scopes",
+        "providerId": "allowed-client-templates",
+        "subComponents": {},
+        "subType": "anonymous"
+      },
+      {
+        "config": {
+          "allowed-protocol-mapper-types": [
+            "saml-user-property-mapper",
+            "oidc-full-name-mapper",
+            "oidc-address-mapper",
+            "oidc-usermodel-property-mapper",
+            "oidc-sha256-pairwise-sub-mapper",
+            "saml-role-list-mapper",
+            "saml-user-attribute-mapper",
+            "oidc-usermodel-attribute-mapper"
+          ]
+        },
+        "id": "03d3362c-0f3c-4d47-a983-1a14cd383bb0",
+        "name": "Allowed Protocol Mapper Types",
+        "providerId": "allowed-protocol-mappers",
+        "subComponents": {},
+        "subType": "anonymous"
+      }
+    ]
+  },
+  "defaultDefaultClientScopes": [
+    "role_list",
+    "saml_organization",
+    "profile",
+    "email",
+    "roles",
+    "web-origins",
+    "acr",
+    "basic"
+  ],
+  "defaultOptionalClientScopes": [
+    "offline_access",
+    "address",
+    "phone",
+    "microprofile-jwt",
+    "organization"
+  ],
+  "defaultRole": {
+    "clientRole": false,
+    "composite": true,
+    "containerId": "2f6145a3-ea79-4b1e-bf79-ee52fd535391",
+    "description": "${role_default-roles}",
+    "id": "ffe65c82-b0e5-4f42-b2cc-3f887563c6a8",
+    "name": "default-roles-camel"
+  },
+  "defaultSignatureAlgorithm": "RS256",
+  "directGrantFlow": "direct grant",
+  "dockerAuthenticationFlow": "docker auth",
+  "duplicateEmailsAllowed": false,
+  "editUsernameAllowed": false,
+  "enabled": true,
+  "enabledEventTypes": [],
+  "eventsEnabled": false,
+  "eventsListeners": [
+    "jboss-logging"
+  ],
+  "failureFactor": 30,
+  "firstBrokerLoginFlow": "first broker login",
+  "id": "2f6145a3-ea79-4b1e-bf79-ee52fd535391",
+  "identityProviderMappers": [],
+  "identityProviders": [],
+  "internationalizationEnabled": false,
+  "keycloakVersion": "26.1.2",
+  "localizationTexts": {},
+  "loginWithEmailAllowed": true,
+  "maxDeltaTimeSeconds": 43200,
+  "maxFailureWaitSeconds": 900,
+  "maxTemporaryLockouts": 0,
+  "minimumQuickLoginWaitSeconds": 60,
+  "notBefore": 0,
+  "oauth2DeviceCodeLifespan": 600,
+  "oauth2DevicePollingInterval": 5,
+  "offlineSessionIdleTimeout": 2592000,
+  "offlineSessionMaxLifespan": 5184000,
+  "offlineSessionMaxLifespanEnabled": false,
+  "organizationsEnabled": false,
+  "otpPolicyAlgorithm": "HmacSHA1",
+  "otpPolicyCodeReusable": false,
+  "otpPolicyDigits": 6,
+  "otpPolicyInitialCounter": 0,
+  "otpPolicyLookAheadWindow": 1,
+  "otpPolicyPeriod": 30,
+  "otpPolicyType": "totp",
+  "otpSupportedApplications": [
+    "totpAppFreeOTPName",
+    "totpAppGoogleName",
+    "totpAppMicrosoftAuthenticatorName"
+  ],
+  "permanentLockout": false,
+  "quickLoginCheckMilliSeconds": 1000,
+  "realm": "camel",
+  "refreshTokenMaxReuse": 0,
+  "registrationAllowed": false,
+  "registrationEmailAsUsername": false,
+  "registrationFlow": "registration",
+  "rememberMe": false,
+  "requiredActions": [
+    {
+      "alias": "CONFIGURE_TOTP",
+      "config": {},
+      "defaultAction": false,
+      "enabled": true,
+      "name": "Configure OTP",
+      "priority": 10,
+      "providerId": "CONFIGURE_TOTP"
+    },
+    {
+      "alias": "TERMS_AND_CONDITIONS",
+      "config": {},
+      "defaultAction": false,
+      "enabled": false,
+      "name": "Terms and Conditions",
+      "priority": 20,
+      "providerId": "TERMS_AND_CONDITIONS"
+    },
+    {
+      "alias": "UPDATE_PASSWORD",
+      "config": {},
+      "defaultAction": false,
+      "enabled": true,
+      "name": "Update Password",
+      "priority": 30,
+      "providerId": "UPDATE_PASSWORD"
+    },
+    {
+      "alias": "UPDATE_PROFILE",
+      "config": {},
+      "defaultAction": false,
+      "enabled": true,
+      "name": "Update Profile",
+      "priority": 40,
+      "providerId": "UPDATE_PROFILE"
+    },
+    {
+      "alias": "VERIFY_EMAIL",
+      "config": {},
+      "defaultAction": false,
+      "enabled": true,
+      "name": "Verify Email",
+      "priority": 50,
+      "providerId": "VERIFY_EMAIL"
+    },
+    {
+      "alias": "delete_account",
+      "config": {},
+      "defaultAction": false,
+      "enabled": false,
+      "name": "Delete Account",
+      "priority": 60,
+      "providerId": "delete_account"
+    },
+    {
+      "alias": "webauthn-register",
+      "config": {},
+      "defaultAction": false,
+      "enabled": true,
+      "name": "Webauthn Register",
+      "priority": 70,
+      "providerId": "webauthn-register"
+    },
+    {
+      "alias": "webauthn-register-passwordless",
+      "config": {},
+      "defaultAction": false,
+      "enabled": true,
+      "name": "Webauthn Register Passwordless",
+      "priority": 80,
+      "providerId": "webauthn-register-passwordless"
+    },
+    {
+      "alias": "VERIFY_PROFILE",
+      "config": {},
+      "defaultAction": false,
+      "enabled": true,
+      "name": "Verify Profile",
+      "priority": 90,
+      "providerId": "VERIFY_PROFILE"
+    },
+    {
+      "alias": "delete_credential",
+      "config": {},
+      "defaultAction": false,
+      "enabled": true,
+      "name": "Delete Credential",
+      "priority": 100,
+      "providerId": "delete_credential"
+    },
+    {
+      "alias": "update_user_locale",
+      "config": {},
+      "defaultAction": false,
+      "enabled": true,
+      "name": "Update User Locale",
+      "priority": 1000,
+      "providerId": "update_user_locale"
+    }
+  ],
+  "requiredCredentials": [
+    "password"
+  ],
+  "resetCredentialsFlow": "reset credentials",
+  "resetPasswordAllowed": false,
+  "revokeRefreshToken": false,
+  "scopeMappings": [
+    {
+      "clientScope": "offline_access",
+      "roles": [
+        "offline_access"
+      ]
+    }
+  ],
+  "smtpServer": {},
+  "sslRequired": "external",
+  "ssoSessionIdleTimeout": 1800,
+  "ssoSessionIdleTimeoutRememberMe": 0,
+  "ssoSessionMaxLifespan": 36000,
+  "ssoSessionMaxLifespanRememberMe": 0,
+  "supportedLocales": [],
+  "userManagedAccessAllowed": false,
+  "users": [
+    {
+      "createdTimestamp": 1739866545416,
+      "disableableCredentialTypes": [],
+      "emailVerified": false,
+      "enabled": true,
+      "id": "b69d3c34-dda3-47b6-9ce9-0010f756cde4",
+      "notBefore": 0,
+      "requiredActions": [],
+      "serviceAccountClientId": "camel-client",
+      "totp": false,
+      "username": "service-account-camel-client"
+    },
+    {
+      "username": "alice",
+      "enabled": true,
+      "email": "alice@example.com",
+      "emailVerified": true,
+      "firstName": "Alice",
+      "lastName": "Brown",
+      "credentials": [
+        {
+          "type": "password",
+          "value": "alice"
+        }
+      ]
+    }
+  ],
+  "verifiableCredentialsEnabled": false,
+  "verifyEmail": false,
+  "waitIncrementSeconds": 60,
+  "webAuthnPolicyAcceptableAaguids": [],
+  "webAuthnPolicyAttestationConveyancePreference": "not specified",
+  "webAuthnPolicyAuthenticatorAttachment": "not specified",
+  "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
+  "webAuthnPolicyCreateTimeout": 0,
+  "webAuthnPolicyExtraOrigins": [],
+  "webAuthnPolicyPasswordlessAcceptableAaguids": [],
+  "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
+  "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
+  "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
+  "webAuthnPolicyPasswordlessCreateTimeout": 0,
+  "webAuthnPolicyPasswordlessExtraOrigins": [],
+  "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
+  "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
+  "webAuthnPolicyPasswordlessRpId": "",
+  "webAuthnPolicyPasswordlessSignatureAlgorithms": [
+    "ES256",
+    "RS256"
+  ],
+  "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
+  "webAuthnPolicyRequireResidentKey": "not specified",
+  "webAuthnPolicyRpEntityName": "keycloak",
+  "webAuthnPolicyRpId": "",
+  "webAuthnPolicySignatureAlgorithms": [
+    "ES256",
+    "RS256"
+  ],
+  "webAuthnPolicyUserVerificationRequirement": "not specified"
+}
diff --git a/components/camel-oauth/helm/etc/keycloak.crt b/components/camel-oauth/helm/etc/keycloak.crt
new file mode 100644
index 00000000000..09b59c8d3e3
--- /dev/null
+++ b/components/camel-oauth/helm/etc/keycloak.crt
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFyzCCA7OgAwIBAgIUFVvwaM91yqF60c5zGcXkBfmTRKEwDQYJKoZIhvcNAQEL
+BQAwazELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
+MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xDTALBgNVBAsMBFVuaXQxFzAVBgNVBAMM
+DmtleWNsb2FrLmxvY2FsMB4XDTI1MDIxNzEwNDkxMFoXDTI2MDIxNzEwNDkxMFow
+azELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRUw
+EwYDVQQKDAxPcmdhbml6YXRpb24xDTALBgNVBAsMBFVuaXQxFzAVBgNVBAMMDmtl
+eWNsb2FrLmxvY2FsMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqr5n
+fYTJJ+4O7naJ59FPxAlz4vshjpUgLlizDQ3VgL20lS/XDtX/sId50LFTYJiXeh0p
+TDPSzVYJr6s5mMfOgLxdgLlp6d2kbj2FVhlDKLdkUnELdro/yLuUbLcwI/brNiTO
+JWo5UbneL8RWuwsOCSI0YHiKX3T+xq3S6fZ2Sp7pZeiNoMXsDKh6t9TQRtvOwTPz
+z6DWSpeEEfNpx4HsSp0DMkL+sEnxbX0eDBGsQbCr3NEvxFttZoASpXkvTEmbPs9n
+Ebm7LYRjthIbGUfRP1DMPBR1RcFhqNpwERGAzhTe+2FLlSFNjsKo3JNyo+hhCUhW
+Z773qacai3lZO3oNt514Ic/NzsHuG1+Ge4VXWjyVUqjBkxXzrwvWO6AuVAwJOnWo
+w9ys3r3M622/KB6SWtdC1WkRlRc9dt9G8WceRv4LAUv5qt7dmS8+pq0UTBoLn5DS
+2TJBwlSkpi5vWEIUeKRl/qUfKzRr/sDLMjC/qrlsWPkFS9+ooFsWBtuGNz29T0b7
+gXGplA05hy5QM/biLxWLK0SgqpQ/znIOAT93i4f/cM1EfleF8yFqELd+LzfHlJka
+2CQfOY0cwsKKUPdbLlMt/VddvNILN84RrtbbwwJ55poRiBSfx1lkODw5sm4lTPF8
+f8XOo8wwlmoVkn9rJlqkq5xZBlAsIW+8dwt8vA8CAwEAAaNnMGUwDgYDVR0PAQH/
+BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdEQQYMBaCDmtleWNsb2Fr
+LmxvY2FshwR/AAABMB0GA1UdDgQWBBQ6xZaBawT/6XxXK5bMcJNqyE7OgjANBgkq
+hkiG9w0BAQsFAAOCAgEAXGk3i4C8tmcqgR48ziU6WIE/lFcOfcd8ITahdCvrgMLv
+yUYO/POS24fi+djWmMvBYojiOPuvY8EyKyg2+CSAfmMRxcZIQHHz8EG8awzZwzVf
+IamGKo2rVdB/p3wgut9EreGkHQ9jpPM0TFwT/ei4GULjYIrhcqjDNyOCtGaIn5Qt
+Mm3IghOLfrEj7WYwNSBhpC++hd6Q7FXKwa41Dgar6GKjDn2QHEgPGks225MX+WPl
+ZbCdJO9+Heez3UejDJ484ZuxgxpbMgG2Uq1knaE+K5yIbk2h/GlX0nceMb0r7Wbb
+53IennDQEZETG2k3tMSF4ez2WLHOP993T7zZVs5Wfl7hpQZ7yA4LteOPFCE0KcfC
+zj0Svz+w5Iu5GSbVUpG6qSxl5S/xEVsUYwZJvlKfz4reentd8Z717fhaxtaUZxKm
+SoCkMVbAwiolBVgeOkvpasXd1q+rrwCEJYjUK9T5hThTXtQThKii0FgE3n91JAnC
+Is/dCvLDea8SYlpiyVpaC6sxFtZr99GDE+Vj9radPAJWQl2XO5o5uiCdvqVugXvy
+p2N3HJAQdklSEkUcl7xE73sGjfI1Wpdi/KYiFsiLmm/+hymug7S0Ll9X6mHbakvk
+nEZlZVWg0HXYhpI+LsUPCca+qEOxOEwqNKtL0CKjfcyYhOn18pRHCQr7AFJeZYs=
+-----END CERTIFICATE-----
diff --git a/components/camel-oauth/helm/etc/keycloak.key b/components/camel-oauth/helm/etc/keycloak.key
new file mode 100644
index 00000000000..154101357fb
--- /dev/null
+++ b/components/camel-oauth/helm/etc/keycloak.key
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCqvmd9hMkn7g7u
+donn0U/ECXPi+yGOlSAuWLMNDdWAvbSVL9cO1f+wh3nQsVNgmJd6HSlMM9LNVgmv
+qzmYx86AvF2AuWnp3aRuPYVWGUMot2RScQt2uj/Iu5RstzAj9us2JM4lajlRud4v
+xFa7Cw4JIjRgeIpfdP7GrdLp9nZKnull6I2gxewMqHq31NBG287BM/PPoNZKl4QR
+82nHgexKnQMyQv6wSfFtfR4MEaxBsKvc0S/EW21mgBKleS9MSZs+z2cRubsthGO2
+EhsZR9E/UMw8FHVFwWGo2nAREYDOFN77YUuVIU2Owqjck3Kj6GEJSFZnvveppxqL
+eVk7eg23nXghz83Owe4bX4Z7hVdaPJVSqMGTFfOvC9Y7oC5UDAk6dajD3Kzevczr
+bb8oHpJa10LVaRGVFz1230bxZx5G/gsBS/mq3t2ZLz6mrRRMGgufkNLZMkHCVKSm
+Lm9YQhR4pGX+pR8rNGv+wMsyML+quWxY+QVL36igWxYG24Y3Pb1PRvuBcamUDTmH
+LlAz9uIvFYsrRKCqlD/Ocg4BP3eLh/9wzUR+V4XzIWoQt34vN8eUmRrYJB85jRzC
+wopQ91suUy39V1280gs3zhGu1tvDAnnmmhGIFJ/HWWQ4PDmybiVM8Xx/xc6jzDCW
+ahWSf2smWqSrnFkGUCwhb7x3C3y8DwIDAQABAoICAD5z+b+4J6nS0iRYOHYSIhaH
+DJvsFqikFErPh+KQ+lSmTxy4jHWxQd6slxqGGgS9m75/VyE33XXc8BsQyrb+8qI1
+BBB3AW4Y87GdEB3fjKiid/ANyE8rrfP65L3lPZcSFJPP35Vg5sllbodTThQJZZ9i
+ovzcamSjEyPR5dFNJJSDzsRHTTKO4pn4sDZ4gZxBfz09BwOdhXRwOk8klT7z312l
+U8t5JNaPnV7agniQqEy41gwlec7dUEVLOEbNbkb9r2rIix85K+VoxN4Ofln/YmLB
+54pcQ075/oI4MACWQeJfUPZq7q4EKe27du+DgU3BzGiMTIqTF9y2sidtAf4W1VgH
+1Pi4dIhcy4wKTVY7vu1dHjJfLgScecHUc09OXqBz1ChCJ+N4Wm1Ft0GawWIP8W23
+BuoWZXuCJYvN7QUPhypNQe+v2tlI0E0VxWMU25SPB7zs4ugXZxhqagDOEuUIb61T
+zSGQTCdJuapFQr+wm1UEdBYTnSKezIM3vToQ/tAviRgGv8r0nYSMBgaO/A3OIHWW
+RiPVlPvTDf+A3f97atuEKnr84gs8ONEISneN+tDdWIgZKscnhIO/77IAyBO913rT
+ZInnUUwizpIbg3D30hWnr+QF6R3/wZ49OcA6P4TS4KjFexuaMxnQsr/Rpxr85vXn
+SFx+hYXaOPyuDpOQRJXBAoIBAQDoXAC7rODhRCX6vgEVFhwFJYGIS5dTV5kcsfAx
+QXBads8m7W+XmNHBLE5qqs1Lrta1gWzM0beZeuN5+NX+zT4f/ey6TSVFy78jA033
+xYZCD64tq04aTwAIVSzl6GbGESFH7NbuIOiCYvK1YYgEeJb+ShVXq/JES17b0v6P
+bfN0CJIqBgaXXNv6k+niJeyYgoXKkBLyM4C1yUq2yG0CPnoFfJjA9VXqtmioDwae
+uSM330Dgjeu0/X2Ie7NvkPHIuyF15P/tlqIw/4JBSVG1JR58hsmzS62lMuargIki
+ufiY3PXZOmaAEt0BUL/rIBS/chTIrO9nKCpXZYHUkLMGbEnPAoIBAQC8HZIDdfco
+58L/2/e7qlT9Yhlxfdnb0dj8Ibfb2vzGSekqaON7j2P5U8GHqevGxIy38XFwWI6P
+Df/KmcEeUSHwH5PbwZR+bz0HTxa//ZxU9h4ovt1WVcTnXAOqXSSoBTktIHbvW0K+
+q2UBHMCqSzFQqN3KNLkC9TerX5zH0PpsIstJSnKsX5kwDpzoWXOjURTquFkRiQmj
+aHW+NsS2r6sp/XJRbPUWKctyMJXY5zuuY0jIeuS1tFZyg89NOk6tlZJwDYeKmpqK
+NKu1QGT/5DIYsbBf7FkSg4M4ebPfj5LDi1tcEscTVISLl9vfwIUn68DpbGS37WC4
+Az7uTTZOdjnBAoIBAQDWBbBTLaixNUE6VPrQPl4uy14leWL4TC5mxOs1o03mtZqX
+A4qO+x7xLGtKSurv9ComYwEhI2ZWg1+1wGLz4H6BnzUGYPqc8E+s42VUfGbuwFyy
+CiDVkMC57CJ/3fGULMEX6+oIytz/BD8EnMJZQ+3oabqA14s+JqJIHm+FE0R0M1TW
+SliV4N1qUAXU3ROxYj/z1YLt/kxmpPMhqzjlIUDpftuAmDm8CKobakOxvVvYIdYz
+P+XE/tw6853SfJKITv1CB11IrT/Vm/ORSQRusrMDBnAQqvtVt63KeIlYKC34N0Zo
+ASfafgV9KbCs1Q7nTCAiu0+EF/XwLDpK3cFYgDP3AoIBAQCCrewcGVvcf7v38AAV
+z0sBRp+0NqAcU5fv9yB5S5Rjzz555ql40XC66JM9wi33Q90Zf1G1oN9Pw4LZZJxC
+ikwQOm+FruGQP4hexnNgZjau0ta6qXSrICQMMOCe/duUGSTSE4Zr2rLwL7KX5jh1
+VIzqopSIfr2zr1O/6kjIioUcj8M2o9ArrLGHL++SfWmWn6HvOKcvRkp6yPtaa0OW
+QUyfe+Bpbk6H2frp1ZSTPHc6OuB53N162M9g9+4PkRzM+2XiD5QEFkibD+HkpNpE
+5WpjuzRyPobWFKQYnHr3KA7lZq7NtIG1DPaS3qiwtFYMGHtH7LO/Ddktm5uhAeRi
+90BBAoIBADc3wftpVYj2shAmU+0SSxYRHSXPb3Kgha/vrihNHLvp2ZwBp//6jXuk
+5ZpNgwe8T3Z6VAfvD1RXgvmb1GQviHCmtzsKVjgJnsxj8sOvpMBAvzupg6fxjG5R
+nJX9Vpks4TEunhnIWWGibEvbu7KKQXl/Wy9XDQdEsX9sjwnW5e0uAstWngHRFJOY
+u1KF4yfH/XhbKFLqku4s/xtNd/rAijfxs/k0BA5r+JVdoJ6ZykEe3hBRTp1DJ725
+hfg4yNcELG25uwcvaTeKnph1TqdFRwC37Kb+QNNG7GYpB+g7QsH4fLFAuZRVYHxo
+NllR/NTnsleE+4jwx8GMlvFvto+9/aw=
+-----END PRIVATE KEY-----
diff --git a/components/camel-oauth/helm/etc/san.cnf b/components/camel-oauth/helm/etc/san.cnf
new file mode 100644
index 00000000000..110a13cd86d
--- /dev/null
+++ b/components/camel-oauth/helm/etc/san.cnf
@@ -0,0 +1,22 @@
+[ req ]
+default_bits       = 4096
+distinguished_name = req_distinguished_name
+x509_extensions    = v3_req
+prompt             = no
+
+[ req_distinguished_name ]
+C  = US
+ST = State
+L  = City
+O  = Organization
+OU = Unit
+CN = keycloak.local
+
+[ v3_req ]
+keyUsage            = critical, digitalSignature, keyEncipherment
+extendedKeyUsage    = serverAuth
+subjectAltName      = @alt_names
+
+[ alt_names ]
+DNS.1 = keycloak.local
+IP.1  = 127.0.0.1
diff --git a/components/camel-oauth/helm/templates/keycloak.yaml b/components/camel-oauth/helm/templates/keycloak.yaml
new file mode 100644
index 00000000000..94ffd0c57c4
--- /dev/null
+++ b/components/camel-oauth/helm/templates/keycloak.yaml
@@ -0,0 +1,127 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+{{- with .Values.deployments.keycloak }}
+
+# Keycloak Realm ConfigMap
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: keycloak-realm-config
+  labels:
+    app.kubernetes.io/name: keycloak
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/release: {{ $.Release.Name }}
+    app.kubernetes.io/revision: '{{ $.Release.Revision }}'
+data:
+  demo-realm.json: |-
+    {{- $.Files.Get "etc/camel-realm.json" | nindent 4 }}
+
+# Keycloak TLS Secret
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: keycloak-tls
+  labels:
+    app.kubernetes.io/name: keycloak
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/release: {{ $.Release.Name }}
+    app.kubernetes.io/revision: '{{ $.Release.Revision }}'
+type: kubernetes.io/tls
+data:
+  tls.crt: {{ $.Files.Get "etc/keycloak.crt" | b64enc }}
+  tls.key: {{ $.Files.Get "etc/keycloak.key" | b64enc }}
+
+# Keycloak Deployment
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: keycloak
+  labels:
+    app.kubernetes.io/name: keycloak
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/release: {{ $.Release.Name }}
+    app.kubernetes.io/revision: '{{ $.Release.Revision }}'
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: keycloak
+  template:
+    metadata:
+      labels:
+        app.kubernetes.io/name: keycloak
+        app.kubernetes.io/managed-by: Helm
+        app.kubernetes.io/release: {{ $.Release.Name }}
+        app.kubernetes.io/revision: '{{ $.Release.Revision }}'
+    spec:
+      containers:
+        - name: keycloak
+          image: quay.io/keycloak/keycloak:{{ .version }}
+          args:
+            - "start-dev"
+            - "--https-port=8443"
+            - "--https-certificate-file=/etc/x509/https/tls.crt"
+            - "--https-certificate-key-file=/etc/x509/https/tls.key"
+            - "--import-realm"
+          env:
+            - name: KC_BOOTSTRAP_ADMIN_USERNAME
+              value: admin
+            - name: KC_BOOTSTRAP_ADMIN_PASSWORD
+              value: admin
+          ports:
+            - containerPort: 8080
+            - containerPort: 8443
+          volumeMounts:
+            - name: keycloak-tls
+              mountPath: /etc/x509/https
+              readOnly: true
+            - name: keycloak-realm-config
+              mountPath: /opt/keycloak/data/import
+              readOnly: true
+      volumes:
+        - name: keycloak-tls
+          secret:
+            secretName: keycloak-tls
+        - name: keycloak-realm-config
+          configMap:
+            name: keycloak-realm-config
+
+# Keycloak Service
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: keycloak
+  labels:
+    app.kubernetes.io/name: keycloak
+spec:
+  type: NodePort
+  ports:
+    - name: http
+      port: 8080
+      nodePort: {{ .nodePortHttp }}
+    - name: https
+      port: 8443
+      nodePort: {{ .nodePortHttps }}
+  selector:
+    app.kubernetes.io/name: keycloak
+---
+{{- end }}
diff --git a/components/camel-oauth/helm/values-keycloak.yaml b/components/camel-oauth/helm/values-keycloak.yaml
new file mode 100644
index 00000000000..3cf2797d207
--- /dev/null
+++ b/components/camel-oauth/helm/values-keycloak.yaml
@@ -0,0 +1,25 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+deployments:
+
+  keycloak:
+    name: "keycloak"
+    version: 26.1.2
+    nodePortHttp:  30100
+    nodePortHttps: 30443
+
diff --git a/components/camel-oauth/pom.xml b/components/camel-oauth/pom.xml
new file mode 100644
index 00000000000..0723d275b48
--- /dev/null
+++ b/components/camel-oauth/pom.xml
@@ -0,0 +1,133 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>components</artifactId>
+        <version>4.11.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>camel-oauth</artifactId>
+
+    <name>Camel :: OAuth</name>
+    <description>Camel OAuth (Preview)</description>
+
+    <properties>
+        <keycloak.client.version>26.0.4</keycloak.client.version>
+        <supportLevel>preview</supportLevel>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>nimbus-jose-jwt</artifactId>
+            <version>${nimbus-jose-jwt}</version>
+        </dependency>
+
+        <!-- https://vertx.io/docs/vertx-auth-oauth2/java/ -->
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-auth-oauth2</artifactId>
+            <version>${vertx-version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+        </dependency>
+
+        <!-- Provided Dependencies -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-platform-http-main</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j2-impl</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-jackson</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-vertx-http</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-admin-client</artifactId>
+            <version>${keycloak.client.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>com.mycila</groupId>
+                <artifactId>license-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>format</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <licenseSets>
+                        <licenseSet>
+                            <excludes>
+                                <exclude>helm/etc/*</exclude>
+                            </excludes>
+                        </licenseSet>
+                    </licenseSets>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/components/camel-oauth/src/generated/resources/META-INF/services/org/apache/camel/other.properties b/components/camel-oauth/src/generated/resources/META-INF/services/org/apache/camel/other.properties
new file mode 100644
index 00000000000..b52adefab4d
--- /dev/null
+++ b/components/camel-oauth/src/generated/resources/META-INF/services/org/apache/camel/other.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+name=oauth
+groupId=org.apache.camel
+artifactId=camel-oauth
+version=4.11.0-SNAPSHOT
+projectName=Camel :: OAuth
+projectDescription=Camel OAuth (Preview)
diff --git a/components/camel-oauth/src/generated/resources/oauth.json b/components/camel-oauth/src/generated/resources/oauth.json
new file mode 100644
index 00000000000..2e0a95e0551
--- /dev/null
+++ b/components/camel-oauth/src/generated/resources/oauth.json
@@ -0,0 +1,13 @@
+{
+  "other": {
+    "kind": "other",
+    "name": "oauth",
+    "title": "Oauth",
+    "description": "Camel OAuth (Preview)",
+    "deprecated": false,
+    "supportLevel": "Preview",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-oauth",
+    "version": "4.11.0-SNAPSHOT"
+  }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java
new file mode 100644
index 00000000000..338567e55bb
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.Processor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_COOKIE;
+
+public abstract class AbstractOAuthProcessor implements Processor {
+
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    protected final String procName = getClass().getSimpleName();
+
+    protected Optional<OAuth> findOAuth(CamelContext camelContext) {
+        return OAuthFactory.getOAuthFactory(camelContext).findOAuth(camelContext);
+    }
+
+    protected OAuth findOAuthOrThrow(CamelContext context) {
+        return findOAuth(context).orElseThrow(() -> new NoSuchElementException("No OAuth"));
+    }
+
+    protected void authenticateExistingUserProfile(OAuth oauth, OAuthSession session) {
+        // Remove before attempting to re-authenticate
+        var userProfile = session.removeUserProfile().orElseThrow();
+        if (userProfile.ttl() < 0L) {
+            userProfile = oauth.refresh(userProfile);
+            userProfile.logDetails("Refreshed");
+        } else {
+            var creds = new UserCredentials(userProfile);
+            var updProfile = oauth.authenticate(creds);
+            updProfile.merge(updProfile);
+            userProfile.logDetails("ReAuthenticated");
+        }
+        session.putUserProfile(userProfile);
+    }
+
+    protected void logRequestHeaders(String msgPrefix, Message msg) {
+        log.debug("{} - Request headers ...", msgPrefix);
+        msg.getHeaders().forEach((k, v) -> {
+            log.debug("   {}: {}", k, v);
+        });
+    }
+
+    protected void sendRedirect(Message msg, String redirectUrl) {
+        log.debug("Redirect to: {}", redirectUrl);
+        msg.setHeader(Exchange.HTTP_RESPONSE_CODE, 302);
+        msg.setHeader("Location", redirectUrl);
+        msg.setBody("");
+    }
+
+    protected void setSessionCookie(Message msg, OAuthSession session) {
+        var sessionId = session.getSessionId();
+        var cookie = "%s=%s; Path=/; HttpOnly; SameSite=None; Secure".formatted(CAMEL_OAUTH_COOKIE, sessionId);
+        msg.setHeader("Set-Cookie", cookie);
+        log.debug("Set-Cookie: {}", cookie);
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/AuthCodeCredentials.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/AuthCodeCredentials.java
new file mode 100644
index 00000000000..3ea63840c3e
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/AuthCodeCredentials.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+public class AuthCodeCredentials implements Credentials {
+
+    private String code;
+    private String redirectUri;
+
+    public String getCode() {
+        return code;
+    }
+
+    public AuthCodeCredentials setCode(String code) {
+        this.code = code;
+        return this;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public AuthCodeCredentials setRedirectUri(String redirectUri) {
+        this.redirectUri = redirectUri;
+        return this;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/ClientCredentials.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/ClientCredentials.java
new file mode 100644
index 00000000000..defc8c8b114
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/ClientCredentials.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+public class ClientCredentials implements Credentials {
+
+    private String clientId;
+    private String clientSecret;
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public ClientCredentials setClientId(String clientId) {
+        this.clientId = clientId;
+        return this;
+    }
+
+    public String getClientSecret() {
+        return clientSecret;
+    }
+
+    public ClientCredentials setClientSecret(String clientSecret) {
+        this.clientSecret = clientSecret;
+        return this;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/Credentials.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/Credentials.java
new file mode 100644
index 00000000000..4b123b97cd0
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/Credentials.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+public interface Credentials {
+
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySession.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySession.java
new file mode 100644
index 00000000000..7f96495aa51
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySession.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class InMemorySession implements OAuthSession {
+
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final String sessionId;
+    private final Map<String, Object> values = new HashMap<>();
+
+    public InMemorySession(String sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    @Override
+    public String getSessionId() {
+        return sessionId;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> Optional<T> getValue(String key, Class<T> clazz) {
+        return (Optional<T>) Optional.ofNullable(values.get(key));
+    }
+
+    @Override
+    public <T> void putValue(String key, T value) {
+        values.put(key, value);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> Optional<T> removeValue(String key) {
+        var maybeValue = getValue(key, Object.class);
+        values.remove(key);
+        return (Optional<T>) maybeValue;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java
new file mode 100644
index 00000000000..04ad43bc01a
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import org.apache.camel.Exchange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_COOKIE;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_SESSION_ID;
+
+public class InMemorySessionStore implements OAuthSessionStore {
+
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final Map<String, OAuthSession> sessions = new HashMap<>();
+
+    @Override
+    public Optional<OAuthSession> getSession(Exchange exchange) {
+        var msg = exchange.getMessage();
+
+        // First, try the CamelOAuthSessionId header
+        var sessionId = msg.getHeader(CAMEL_OAUTH_SESSION_ID, String.class);
+
+        // Fallback to our session cookie
+        if (sessionId == null) {
+            var cookies = getCookies(exchange);
+            if (cookies.get(CAMEL_OAUTH_COOKIE) == null) {
+                log.warn("No '{}' Cookie in HTTP request", CAMEL_OAUTH_COOKIE);
+                return Optional.empty();
+            }
+            sessionId = cookies.get(CAMEL_OAUTH_COOKIE);
+        }
+
+        var maybeSession = Optional.ofNullable(sessions.get(sessionId));
+        if (maybeSession.isEmpty()) {
+            log.warn("No OAuthSession for: {}", sessionId);
+        }
+
+        return maybeSession;
+    }
+
+    public OAuthSession createSession(Exchange exchange) {
+
+        var sessionId = UUID.randomUUID().toString();
+        var session = new InMemorySession(sessionId);
+        sessions.put(sessionId, session);
+
+        var msg = exchange.getMessage();
+        log.info("New OAuthSession: {}", sessionId);
+        msg.setHeader(OAuth.CAMEL_OAUTH_SESSION_ID, sessionId);
+
+        return session;
+    }
+
+    private Map<String, String> getCookies(Exchange exchange) {
+
+        var msg = exchange.getMessage();
+        var maybeCookie = Optional.ofNullable(msg.getHeader("Cookie"));
+        if (maybeCookie.isEmpty()) {
+            log.warn("No Cookie in HTTP request");
+            return Map.of();
+        }
+
+        var value = maybeCookie.get().toString();
+        var cookieMap = Arrays.stream(value.split(";"))
+                .map(String::trim)
+                .map(s -> s.split("=", 2))
+                .collect(Collectors.toMap(
+                        arr -> arr[0],
+                        arr -> arr.length > 1 ? arr[1] : ""));
+
+        return cookieMap;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuth.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuth.java
new file mode 100644
index 00000000000..d0b79f9b4eb
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuth.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Optional;
+
+import org.apache.camel.Exchange;
+
+public abstract class OAuth {
+
+    // Camel OAuth Properties
+    //
+    public static final String CAMEL_OAUTH_PROVIDER_BASE_URI = "camel.oauth.baseUri";
+    public static final String CAMEL_OAUTH_CLIENT_ID = "camel.oauth.clientId";
+    public static final String CAMEL_OAUTH_CLIENT_SECRET = "camel.oauth.clientSecret";
+    public static final String CAMEL_OAUTH_LOGOUT_REDIRECT_URI = "camel.oauth.logout.redirectUri";
+    public static final String CAMEL_OAUTH_REDIRECT_URI = "camel.oauth.redirectUri";
+
+    // Camel OAuth Headers
+    //
+    public static final String CAMEL_OAUTH_SESSION_ID = "CamelOAuthSessionId";
+
+    // Camel OAuth Cookies
+    //
+    public static final String CAMEL_OAUTH_COOKIE = "camel.oauth.session";
+
+    // Provider Config -------------------------------------------------------------------------------------------------
+
+    public abstract OAuthConfig discoverOAuthConfig(Exchange exchange) throws OAuthException;
+
+    // OAuth & OIDC user authentication --------------------------------------------------------------------------------
+
+    public abstract UserProfile authenticate(Credentials creds) throws OAuthException;
+
+    public abstract UserProfile refresh(UserProfile user) throws OAuthException;
+
+    public abstract UserProfile tokenRequest(AuthCodeCredentials params) throws OAuthException;
+
+    // OAuth Logout ----------------------------------------------------------------------------------------------------
+
+    public abstract String buildLogoutRequestUrl(OAuthLogoutParams params);
+
+    // OIDC Authorization Code Flow ------------------------------------------------------------------------------------
+
+    public abstract String buildCodeFlowAuthRequestUrl(OAuthCodeFlowParams params);
+
+    // Session management ----------------------------------------------------------------------------------------------
+
+    public Optional<OAuthSession> getSession(Exchange exchange) {
+        return getSessionStore().getSession(exchange);
+    }
+
+    public OAuthSession createSession(Exchange exchange) {
+        return getSessionStore().createSession(exchange);
+    }
+
+    public abstract OAuthSessionStore getSessionStore();
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
new file mode 100644
index 00000000000..afa9a9d69cc
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Optional;
+
+import org.apache.camel.Exchange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OAuthBearerTokenProcessor extends AbstractOAuthProcessor {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public void process(Exchange exchange) {
+        var context = exchange.getContext();
+        var msg = exchange.getMessage();
+
+        logRequestHeaders(procName, msg);
+
+        // Validate Authorization header
+        //
+        var authHeader = msg.getHeader("Authorization", String.class);
+        if (authHeader == null) {
+            log.error("No Authorization header in request");
+            msg.setHeader("CamelHttpResponseCode", 400);
+            msg.setBody("Authorization header");
+            return;
+        }
+
+        var toks = authHeader.split(" ");
+        if (toks.length != 2 || !"Bearer".equals(toks[0])) {
+            log.error("Invalid Authorization header: {}", authHeader);
+            msg.setHeader("CamelHttpResponseCode", 400);
+            msg.setBody("Invalid Authorization header");
+            return;
+        }
+
+        // Find or create the OAuth instance
+        //
+        var oauth = findOAuth(context).orElseGet(() -> {
+            var factory = OAuthFactory.getOAuthFactory(context);
+            return factory.createOAuth(exchange);
+        });
+
+        // Authenticate the bearer's access token
+        //
+        var access_token = toks[1];
+        var userProfile = oauth.authenticate(new TokenCredentials()
+                .setToken(access_token));
+
+        // Get or create the OAuthSession
+        //
+        var session = oauth.getSession(exchange)
+                .or(() -> Optional.of(oauth.createSession(exchange)))
+                .get();
+
+        session.putUserProfile(userProfile);
+        userProfile.logDetails("Authenticated");
+
+        log.info("{} - Done", procName);
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthClientCredentialsProcessor.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthClientCredentialsProcessor.java
new file mode 100644
index 00000000000..47ec5bd8779
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthClientCredentialsProcessor.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Optional;
+
+import org.apache.camel.Exchange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_CLIENT_ID;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_CLIENT_SECRET;
+import static org.apache.camel.oauth.OAuthProperties.getRequiredProperty;
+
+public class OAuthClientCredentialsProcessor extends AbstractOAuthProcessor {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public void process(Exchange exchange) {
+        var context = exchange.getContext();
+        var msg = exchange.getMessage();
+
+        logRequestHeaders(procName, msg);
+
+        // Find or create the OAuth instance
+        //
+        var oauth = findOAuth(context).orElseGet(() -> {
+            var factory = OAuthFactory.getOAuthFactory(context);
+            return factory.createOAuth(exchange);
+        });
+
+        // Get or create the OAuthSession
+        //
+        var session = oauth.getSession(exchange)
+                .or(() -> Optional.of(oauth.createSession(exchange)))
+                .get();
+
+        // Authenticate an existing UserProfile from the OAuthSession
+        //
+        if (session.getUserProfile().isPresent()) {
+            authenticateExistingUserProfile(oauth, session);
+        }
+
+        // Fallback to client credential grant
+        //
+        if (session.getUserProfile().isEmpty()) {
+
+            var clientId = getRequiredProperty(exchange, CAMEL_OAUTH_CLIENT_ID);
+            var clientSecret = getRequiredProperty(exchange, CAMEL_OAUTH_CLIENT_SECRET);
+
+            var userProfile = oauth.authenticate(new ClientCredentials()
+                    .setClientSecret(clientSecret)
+                    .setClientId(clientId));
+
+            session.putUserProfile(userProfile);
+            userProfile.logDetails("Authenticated");
+        }
+
+        // Add Authorization: Bearer <access-token>
+        //
+        session.getUserProfile().ifPresent(userProfile -> {
+            var accessToken = userProfile.accessToken().orElseThrow(() -> new OAuthException("No access_token"));
+            msg.setHeader("Authorization", "Bearer " + accessToken);
+        });
+
+        log.info("{} - Done", procName);
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallbackProcessor.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallbackProcessor.java
new file mode 100644
index 00000000000..4055e3f978c
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallbackProcessor.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import org.apache.camel.Exchange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_REDIRECT_URI;
+import static org.apache.camel.oauth.OAuthProperties.getRequiredProperty;
+
+public class OAuthCodeFlowCallbackProcessor extends AbstractOAuthProcessor {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public void process(Exchange exchange) {
+        var context = exchange.getContext();
+        var msg = exchange.getMessage();
+
+        logRequestHeaders(procName, msg);
+
+        // Validate auth callback request headers/parameters
+        //
+        var authCode = msg.getHeader("code", String.class);
+        if (authCode == null) {
+            log.error("Authorization code is missing in the request");
+            msg.setHeader("CamelHttpResponseCode", 400);
+            msg.setBody("Authorization code missing");
+            return;
+        }
+
+        // Require an active OAuthSession
+        //
+        var oauth = findOAuthOrThrow(context);
+        var session = oauth.getSession(exchange).orElseThrow();
+
+        // Exchange the authorization code for access/refresh/id tokens
+        //
+        String redirectUri = getRequiredProperty(exchange, CAMEL_OAUTH_REDIRECT_URI);
+        var userProfile = oauth.tokenRequest(new AuthCodeCredentials()
+                .setRedirectUri(redirectUri)
+                .setCode(authCode));
+
+        session.putUserProfile(userProfile);
+        userProfile.logDetails("Authenticated");
+
+        var postLoginUrl = (String) session.removeValue("OAuthPostLoginUrl").orElse(null);
+        if (postLoginUrl == null) {
+            postLoginUrl = getRequiredProperty(exchange, CAMEL_OAUTH_REDIRECT_URI);
+            var lastSlashIdx = postLoginUrl.lastIndexOf('/');
+            postLoginUrl = postLoginUrl.substring(0, lastSlashIdx + 1);
+        }
+
+        setSessionCookie(msg, session);
+        sendRedirect(msg, postLoginUrl);
+
+        log.info("{} - Done", procName);
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowParams.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowParams.java
new file mode 100644
index 00000000000..dc8a01b45b5
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowParams.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.List;
+
+public class OAuthCodeFlowParams {
+
+    public enum AuthRequestDisplayOpts {
+        PAGE,
+        POPUP,
+        TOUCH,
+        WAP;
+    }
+
+    public enum AuthRequestPromptOpts {
+        NONE,
+        LOGIN,
+        CONSENT,
+        SELECT_ACCOUNT;
+    }
+
+    public enum AuthRequestResponseType {
+        CODE,
+        ID_TOKEN,
+    }
+
+    private String clientId;
+    private String redirectUri;
+    private AuthRequestResponseType responseType;
+    private List<String> scopes;
+    private String state;
+    private AuthRequestDisplayOpts display;
+    private List<AuthRequestPromptOpts> prompt;
+    private Integer maxAge;
+    private String uiLocales;
+    private String idTokenHint;
+    private String loginHint;
+    private String acrValues;
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public OAuthCodeFlowParams setClientId(String clientId) {
+        this.clientId = clientId;
+        return this;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public OAuthCodeFlowParams setRedirectUri(String redirectUri) {
+        this.redirectUri = redirectUri;
+        return this;
+    }
+
+    public AuthRequestResponseType getResponseType() {
+        return responseType;
+    }
+
+    public OAuthCodeFlowParams setResponseType(AuthRequestResponseType responseType) {
+        this.responseType = responseType;
+        return this;
+    }
+
+    public List<String> getScopes() {
+        return scopes;
+    }
+
+    public OAuthCodeFlowParams setScope(String scope) {
+        this.scopes = List.of(scope);
+        return this;
+    }
+
+    public OAuthCodeFlowParams setScopes(List<String> scopes) {
+        this.scopes = scopes;
+        return this;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public OAuthCodeFlowParams setState(String state) {
+        this.state = state;
+        return this;
+    }
+
+    public AuthRequestDisplayOpts getDisplay() {
+        return display;
+    }
+
+    public OAuthCodeFlowParams setDisplay(AuthRequestDisplayOpts display) {
+        this.display = display;
+        return this;
+    }
+
+    public List<AuthRequestPromptOpts> getPrompt() {
+        return prompt;
+    }
+
+    public OAuthCodeFlowParams setPrompt(List<AuthRequestPromptOpts> prompt) {
+        this.prompt = prompt;
+        return this;
+    }
+
+    public Integer getMaxAge() {
+        return maxAge;
+    }
+
+    public OAuthCodeFlowParams setMaxAge(Integer maxAge) {
+        this.maxAge = maxAge;
+        return this;
+    }
+
+    public String getUiLocales() {
+        return uiLocales;
+    }
+
+    public OAuthCodeFlowParams setUiLocales(String uiLocales) {
+        this.uiLocales = uiLocales;
+        return this;
+    }
+
+    public String getIdTokenHint() {
+        return idTokenHint;
+    }
+
+    public OAuthCodeFlowParams setIdTokenHint(String idTokenHint) {
+        this.idTokenHint = idTokenHint;
+        return this;
+    }
+
+    public String getLoginHint() {
+        return loginHint;
+    }
+
+    public OAuthCodeFlowParams setLoginHint(String loginHint) {
+        this.loginHint = loginHint;
+        return this;
+    }
+
+    public String getAcrValues() {
+        return acrValues;
+    }
+
+    public OAuthCodeFlowParams setAcrValues(String acrValues) {
+        this.acrValues = acrValues;
+        return this;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java
new file mode 100644
index 00000000000..13a65b2638d
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Optional;
+
+import org.apache.camel.Exchange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_REDIRECT_URI;
+import static org.apache.camel.oauth.OAuthProperties.getRequiredProperty;
+
+public class OAuthCodeFlowProcessor extends AbstractOAuthProcessor {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public void process(Exchange exchange) {
+        var context = exchange.getContext();
+        var msg = exchange.getMessage();
+
+        logRequestHeaders(procName, msg);
+
+        // Find or create the OAuth instance
+        //
+        var oauth = findOAuth(context).orElseGet(() -> {
+            var factory = OAuthFactory.getOAuthFactory(context);
+            return factory.createOAuth(exchange);
+        });
+
+        // Get or create the OAuthSession
+        //
+        var session = oauth.getSession(exchange)
+                .or(() -> Optional.of(oauth.createSession(exchange)))
+                .get();
+
+        // Authenticate an existing UserProfile from the OAuthSession
+        //
+        if (session.getUserProfile().isPresent()) {
+            authenticateExistingUserProfile(oauth, session);
+        }
+
+        // Fallback to the authorization code flow
+        //
+        if (session.getUserProfile().isEmpty()) {
+
+            var postLoginUrl = msg.getHeader(Exchange.HTTP_URL, String.class);
+            session.putValue("OAuthPostLoginUrl", postLoginUrl);
+
+            var redirectUri = getRequiredProperty(exchange, CAMEL_OAUTH_REDIRECT_URI);
+            var params = new OAuthCodeFlowParams().setRedirectUri(redirectUri);
+            var authRequestUrl = oauth.buildCodeFlowAuthRequestUrl(params);
+
+            setSessionCookie(msg, session);
+            sendRedirect(msg, authRequestUrl);
+        }
+
+        log.info("{} - Done", procName);
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthConfig.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthConfig.java
new file mode 100644
index 00000000000..b65f45c8501
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthConfig.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+public class OAuthConfig {
+
+    private String baseUrl;
+    private String clientId;
+    private String clientSecret;
+    private String authorizationPath;
+    private String tokenPath;
+    private String revocationPath;
+    private String logoutPath;
+    private String userInfoPath;
+    private String introspectionPath;
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public OAuthConfig setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+        return this;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public OAuthConfig setClientId(String clientId) {
+        this.clientId = clientId;
+        return this;
+    }
+
+    public String getClientSecret() {
+        return clientSecret;
+    }
+
+    public OAuthConfig setClientSecret(String clientSecret) {
+        this.clientSecret = clientSecret;
+        return this;
+    }
+
+    public String getAuthorizationPath() {
+        return authorizationPath;
+    }
+
+    public OAuthConfig setAuthorizationPath(String authorizationPath) {
+        this.authorizationPath = authorizationPath;
+        return this;
+    }
+
+    public String getTokenPath() {
+        return tokenPath;
+    }
+
+    public OAuthConfig setTokenPath(String tokenPath) {
+        this.tokenPath = tokenPath;
+        return this;
+    }
+
+    public String getRevocationPath() {
+        return revocationPath;
+    }
+
+    public OAuthConfig setRevocationPath(String revocationPath) {
+        this.revocationPath = revocationPath;
+        return this;
+    }
+
+    public String getLogoutPath() {
+        return logoutPath;
+    }
+
+    public OAuthConfig setLogoutPath(String logoutPath) {
+        this.logoutPath = logoutPath;
+        return this;
+    }
+
+    public String getUserInfoPath() {
+        return userInfoPath;
+    }
+
+    public OAuthConfig setUserInfoPath(String userInfoPath) {
+        this.userInfoPath = userInfoPath;
+        return this;
+    }
+
+    public String getIntrospectionPath() {
+        return introspectionPath;
+    }
+
+    public OAuthConfig setIntrospectionPath(String introspectionPath) {
+        this.introspectionPath = introspectionPath;
+        return this;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthException.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthException.java
new file mode 100644
index 00000000000..12a60a0cf99
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthException.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+public class OAuthException extends RuntimeException {
+
+    public OAuthException(String msg) {
+        super(msg);
+    }
+
+    public OAuthException(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthFactory.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthFactory.java
new file mode 100644
index 00000000000..66b3af03c30
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Optional;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.oauth.vertx.VertxOAuthFactory;
+
+public abstract class OAuthFactory {
+
+    public static OAuthFactory getOAuthFactory(CamelContext ctx) {
+        var registry = ctx.getRegistry();
+        var factory = registry.lookupByNameAndType(OAuthFactory.class.getName(), OAuthFactory.class);
+        if (factory == null) {
+            // Note, different implementations would require a plugin mechanism
+            factory = new VertxOAuthFactory();
+            registry.bind(OAuthFactory.class.getName(), factory);
+        }
+        return factory;
+    }
+
+    public abstract OAuth createOAuth(Exchange exchange);
+
+    public Optional<OAuth> findOAuth(CamelContext ctx) {
+        var registry = ctx.getRegistry();
+        var oauth = registry.lookupByNameAndType(OAuth.class.getName(), OAuth.class);
+        return Optional.ofNullable(oauth);
+    }
+
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthFlowType.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthFlowType.java
new file mode 100644
index 00000000000..efa0009e39e
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthFlowType.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+public enum OAuthFlowType {
+    AUTH_CODE("authorization_code"),
+    CLIENT_CREDS("client_credentials");
+
+    OAuthFlowType(String flowType) {
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutParams.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutParams.java
new file mode 100644
index 00000000000..0c3e185aff8
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutParams.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+public class OAuthLogoutParams {
+
+    private UserProfile user;
+    private String redirectUri;
+
+    public UserProfile getUser() {
+        return user;
+    }
+
+    public OAuthLogoutParams setUser(UserProfile user) {
+        this.user = user;
+        return this;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public OAuthLogoutParams setRedirectUri(String redirectUri) {
+        this.redirectUri = redirectUri;
+        return this;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutProcessor.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutProcessor.java
new file mode 100644
index 00000000000..890b8a0c2e4
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutProcessor.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import org.apache.camel.Exchange;
+
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_LOGOUT_REDIRECT_URI;
+import static org.apache.camel.oauth.OAuthProperties.getProperty;
+
+public class OAuthLogoutProcessor extends AbstractOAuthProcessor {
+
+    @Override
+    public void process(Exchange exchange) {
+        var context = exchange.getContext();
+
+        findOAuth(context).ifPresent(oauth -> {
+
+            var maybeSession = oauth.getSession(exchange);
+            maybeSession.flatMap(OAuthSession::getUserProfile).ifPresent(user -> {
+
+                maybeSession.get().removeUserProfile();
+
+                var postLogoutUrl = getProperty(exchange, CAMEL_OAUTH_LOGOUT_REDIRECT_URI)
+                        .orElse(null);
+
+                var params = new OAuthLogoutParams()
+                        .setRedirectUri(postLogoutUrl)
+                        .setUser(user);
+
+                var logoutUrl = oauth.buildLogoutRequestUrl(params);
+
+                log.info("OAuth logout: {}", logoutUrl);
+                exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 302);
+                exchange.getMessage().setHeader("Location", logoutUrl);
+                exchange.getMessage().setBody("");
+            });
+        });
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthProperties.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthProperties.java
new file mode 100644
index 00000000000..6e69efbfb44
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthProperties.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+import org.apache.camel.Exchange;
+
+public final class OAuthProperties {
+
+    public static Optional<String> getProperty(Exchange exchange, String key) {
+        var ctx = exchange.getContext();
+        return ctx.getPropertiesComponent().resolveProperty(key);
+    }
+
+    public static String getRequiredProperty(Exchange exchange, String key) {
+        Optional<String> optval = getProperty(exchange, key);
+        return optval.orElseThrow(() -> new NoSuchElementException(key));
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthSession.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthSession.java
new file mode 100644
index 00000000000..30a986f5e50
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthSession.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Optional;
+
+public interface OAuthSession {
+
+    String getSessionId();
+
+    <T> Optional<T> getValue(String key, Class<T> clazz);
+
+    <T> void putValue(String key, T value);
+
+    <T> Optional<T> removeValue(String key);
+
+    default Optional<UserProfile> getUserProfile() {
+        return getValue(UserProfile.class.getName(), UserProfile.class);
+    }
+
+    default void putUserProfile(UserProfile profile) {
+        putValue(UserProfile.class.getName(), profile);
+    }
+
+    default Optional<UserProfile> removeUserProfile() {
+        return removeValue(UserProfile.class.getName());
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthSessionStore.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthSessionStore.java
new file mode 100644
index 00000000000..4220f2c4f88
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthSessionStore.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Optional;
+
+import org.apache.camel.Exchange;
+
+public interface OAuthSessionStore {
+
+    Optional<OAuthSession> getSession(Exchange exchange);
+
+    OAuthSession createSession(Exchange exchange);
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/TokenCredentials.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/TokenCredentials.java
new file mode 100644
index 00000000000..4f6e5b88d68
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/TokenCredentials.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+public class TokenCredentials implements Credentials {
+
+    private String token;
+
+    public String getToken() {
+        return token;
+    }
+
+    public TokenCredentials setToken(String token) {
+        this.token = token;
+        return this;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserCredentials.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserCredentials.java
new file mode 100644
index 00000000000..1aacd3a45ea
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserCredentials.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+public class UserCredentials implements Credentials {
+
+    private final UserProfile userProfile;
+
+    public UserCredentials(UserProfile userProfile) {
+        this.userProfile = userProfile;
+    }
+
+    public UserProfile getUserProfile() {
+        return userProfile;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java
new file mode 100644
index 00000000000..29a7d994d4a
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UserProfile {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final Map<String, Object> attributes;
+    private final Map<String, Object> principal;
+
+    public UserProfile(Map<String, Object> attributes, Map<String, Object> principal) {
+        this.attributes = new LinkedHashMap<>(attributes);
+        this.principal = new LinkedHashMap<>(principal);
+    }
+
+    public Map<String, Object> attributes() {
+        return Collections.unmodifiableMap(attributes);
+    }
+
+    public Map<String, Object> principal() {
+        return Collections.unmodifiableMap(principal);
+    }
+
+    public long ttl() {
+        long now = System.currentTimeMillis() / 1000L;
+        var ttl = Optional.ofNullable(attributes.get("exp"))
+                .map(Object::toString)
+                .map(s -> Long.parseLong(s) - now)
+                .orElse(0L);
+        return ttl;
+    }
+
+    @SuppressWarnings("unchecked")
+    public String subject() {
+        if (principal().containsKey("username")) {
+            return principal().get("username").toString();
+        } else if (principal().containsKey("userHandle")) {
+            return principal().get("userHandle").toString();
+        } else {
+            if (attributes().containsKey("idToken")) {
+                var idToken = (Map<String, Object>) attributes().get("idToken");
+                if (idToken.containsKey("sub")) {
+                    return idToken.get("sub").toString();
+                }
+            }
+            return getClaim("sub");
+        }
+    }
+
+    public Optional<String> accessToken() {
+        return Optional.ofNullable(principal.get("access_token")).map(Object::toString);
+    }
+
+    public Optional<String> idToken() {
+        return Optional.ofNullable(principal.get("id_token")).map(Object::toString);
+    }
+
+    public Optional<String> refreshToken() {
+        return Optional.ofNullable(principal.get("refresh_token")).map(Object::toString);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> T getClaim(String key) {
+        var maybeRootClaim = Optional.ofNullable(attributes().get("rootClaim"))
+                .filter(Map.class::isInstance)
+                .map(Map.class::cast);
+        if (maybeRootClaim.isPresent()) {
+            var rootClaim = maybeRootClaim.get();
+            if (rootClaim.containsKey(key)) {
+                return (T) rootClaim.get(key);
+            }
+        }
+        if (attributes().containsKey(key)) {
+            return (T) attributes().get(key);
+        } else {
+            return (T) (principal().get(key));
+        }
+    }
+
+    public void merge(UserProfile other) {
+        attributes.putAll(other.attributes);
+        principal.putAll(other.principal);
+    }
+
+    public void logDetails(String prefix) {
+        log.info("{}: {}", prefix, subject());
+        log.debug("User Attributes ...");
+        attributes().forEach((k, v) -> log.debug("   {}: {}", k, v));
+        log.debug("User Principal ...");
+        principal().forEach((k, v) -> log.debug("   {}: {}", k, v));
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/vertx/VertxOAuth.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/vertx/VertxOAuth.java
new file mode 100644
index 00000000000..6bb1121e12e
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/vertx/VertxOAuth.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth.vertx;
+
+import io.vertx.core.Vertx;
+import io.vertx.ext.auth.User;
+import io.vertx.ext.auth.authentication.Credentials;
+import io.vertx.ext.auth.authentication.TokenCredentials;
+import io.vertx.ext.auth.oauth2.OAuth2Auth;
+import io.vertx.ext.auth.oauth2.OAuth2AuthorizationURL;
+import io.vertx.ext.auth.oauth2.OAuth2FlowType;
+import io.vertx.ext.auth.oauth2.OAuth2Options;
+import io.vertx.ext.auth.oauth2.Oauth2Credentials;
+import io.vertx.ext.auth.oauth2.providers.OpenIDConnectAuth;
+import org.apache.camel.Exchange;
+import org.apache.camel.oauth.AuthCodeCredentials;
+import org.apache.camel.oauth.InMemorySessionStore;
+import org.apache.camel.oauth.OAuth;
+import org.apache.camel.oauth.OAuthCodeFlowParams;
+import org.apache.camel.oauth.OAuthConfig;
+import org.apache.camel.oauth.OAuthException;
+import org.apache.camel.oauth.OAuthLogoutParams;
+import org.apache.camel.oauth.OAuthSessionStore;
+import org.apache.camel.oauth.UserCredentials;
+import org.apache.camel.oauth.UserProfile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_CLIENT_ID;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_CLIENT_SECRET;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_PROVIDER_BASE_URI;
+import static org.apache.camel.oauth.OAuthProperties.getRequiredProperty;
+
+public class VertxOAuth extends OAuth {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final Vertx vertx;
+    private final OAuthSessionStore sessionStore;
+
+    private OAuth2Auth oauth2;
+    private OAuthConfig config;
+
+    public VertxOAuth(Vertx vertx) {
+        this.vertx = vertx;
+        this.sessionStore = new InMemorySessionStore();
+    }
+
+    @Override
+    public OAuthSessionStore getSessionStore() {
+        return sessionStore;
+    }
+
+    @Override
+    public OAuthConfig discoverOAuthConfig(Exchange exchange) throws OAuthException {
+        if (config == null) {
+            var baseUrl = getRequiredProperty(exchange, CAMEL_OAUTH_PROVIDER_BASE_URI);
+            var clientId = getRequiredProperty(exchange, CAMEL_OAUTH_CLIENT_ID);
+            var clientSecret = getRequiredProperty(exchange, CAMEL_OAUTH_CLIENT_SECRET);
+
+            var config = new OAuthConfig()
+                    .setBaseUrl(baseUrl)
+                    .setClientId(clientId)
+                    .setClientSecret(clientSecret);
+
+            OAuth2Options opts = new OAuth2Options()
+                    .setSite(baseUrl)
+                    .setClientId(config.getClientId())
+                    .setClientSecret(config.getClientSecret());
+
+            try {
+                oauth2 = OpenIDConnectAuth.discover(vertx, opts)
+                        .toCompletionStage()
+                        .toCompletableFuture()
+                        .get();
+            } catch (Exception ex) {
+                throw new OAuthException("Cannot discover OAuth config from: " + baseUrl, ex);
+            }
+
+            config.setAuthorizationPath(opts.getAuthorizationPath())
+                    .setTokenPath(opts.getTokenPath())
+                    .setRevocationPath(opts.getRevocationPath())
+                    .setLogoutPath(opts.getLogoutPath())
+                    .setUserInfoPath(opts.getUserInfoPath())
+                    .setIntrospectionPath(opts.getIntrospectionPath());
+            this.config = config;
+        }
+        return config;
+    }
+
+    @Override
+    public String buildCodeFlowAuthRequestUrl(OAuthCodeFlowParams params) {
+        if (params.getScopes() == null) {
+            params.setScope("openid");
+        }
+        return oauth2.authorizeURL(new OAuth2AuthorizationURL()
+                .setRedirectUri(params.getRedirectUri())
+                .setScopes(params.getScopes()));
+    }
+
+    @Override
+    public UserProfile authenticate(org.apache.camel.oauth.Credentials creds) throws OAuthException {
+
+        Credentials vtxCreds;
+        if (creds instanceof org.apache.camel.oauth.UserCredentials) {
+
+            var userProfile = ((UserCredentials) creds).getUserProfile();
+            log.info("Authenticate userProfile: {}", userProfile.subject());
+            var scope = (String) userProfile.principal().get("scope");
+            vtxCreds = new TokenCredentials()
+                    .setToken(userProfile.accessToken().orElseThrow())
+                    .addScope(scope);
+
+        } else if (creds instanceof org.apache.camel.oauth.AuthCodeCredentials params) {
+
+            vtxCreds = new Oauth2Credentials()
+                    .setFlow(OAuth2FlowType.AUTH_CODE)
+                    .setRedirectUri(params.getRedirectUri())
+                    .setCode(params.getCode());
+
+        } else if (creds instanceof org.apache.camel.oauth.TokenCredentials params) {
+
+            vtxCreds = new TokenCredentials()
+                    .setToken(params.getToken());
+
+        } else if (creds instanceof org.apache.camel.oauth.ClientCredentials params) {
+
+            vtxCreds = new Oauth2Credentials()
+                    .setFlow(OAuth2FlowType.CLIENT)
+                    .setUsername(params.getClientId())
+                    .setPassword(params.getClientSecret());
+
+        } else {
+            throw new OAuthException("Unsupported creds: " + creds.getClass().getName());
+        }
+
+        try {
+            User vtxUser = oauth2.authenticate(vtxCreds)
+                    .toCompletionStage()
+                    .toCompletableFuture()
+                    .get();
+            return new VertxUserProfile(vtxUser);
+        } catch (Exception ex) {
+            throw new OAuthException("Cannot authenticate user", ex);
+        }
+    }
+
+    @Override
+    public UserProfile tokenRequest(AuthCodeCredentials creds) throws OAuthException {
+        UserProfile userProfile = authenticate(creds);
+        return userProfile;
+    }
+
+    @Override
+    public UserProfile refresh(UserProfile user) throws OAuthException {
+
+        log.info("Refresh user: {}", user.subject());
+
+        try {
+            User vtxUser = ((VertxUserProfile) user).getVertxUser();
+            vtxUser = oauth2.refresh(vtxUser)
+                    .toCompletionStage()
+                    .toCompletableFuture()
+                    .get();
+            return new VertxUserProfile(vtxUser);
+        } catch (Exception ex) {
+            throw new OAuthException("Cannot refresh user", ex);
+        }
+    }
+
+    @Override
+    public String buildLogoutRequestUrl(OAuthLogoutParams params) {
+
+        var user = ((VertxUserProfile) params.getUser()).getVertxUser();
+        String endSessionURL = oauth2.endSessionURL(user);
+
+        var postLogoutUrl = params.getRedirectUri();
+        if (postLogoutUrl != null) {
+            endSessionURL += "&post_logout_redirect_uri=" + postLogoutUrl;
+        }
+
+        return endSessionURL;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/vertx/VertxOAuthFactory.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/vertx/VertxOAuthFactory.java
new file mode 100644
index 00000000000..7a3d0bcec4b
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/vertx/VertxOAuthFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth.vertx;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.component.platform.http.vertx.VertxPlatformHttpRouter;
+import org.apache.camel.oauth.OAuth;
+import org.apache.camel.oauth.OAuthFactory;
+
+public final class VertxOAuthFactory extends OAuthFactory {
+
+    // Register BouncyCastle as a security provider
+    // Does not fix: java.security.NoSuchAlgorithmException: RSA-OAEP
+    // Security.addProvider(new BouncyCastleProvider())
+
+    public OAuth createOAuth(Exchange exchange) {
+        var context = exchange.getContext();
+        var registry = context.getRegistry();
+        var router = VertxPlatformHttpRouter.lookup(context);
+        var oauth = new VertxOAuth(router.vertx());
+        oauth.discoverOAuthConfig(exchange);
+        registry.bind(OAuth.class.getName(), oauth);
+        return oauth;
+    }
+}
diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/vertx/VertxUserProfile.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/vertx/VertxUserProfile.java
new file mode 100644
index 00000000000..3c607a40a1b
--- /dev/null
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/vertx/VertxUserProfile.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth.vertx;
+
+import java.text.ParseException;
+import java.util.Map;
+
+import com.nimbusds.jose.util.JSONObjectUtils;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.auth.User;
+import org.apache.camel.oauth.UserProfile;
+
+public class VertxUserProfile extends UserProfile {
+
+    private final User vtxUser;
+
+    public VertxUserProfile(User vtxUser) {
+        super(deepMap(vtxUser.attributes()), deepMap(vtxUser.principal()));
+        this.vtxUser = vtxUser;
+    }
+
+    public User getVertxUser() {
+        return this.vtxUser;
+    }
+
+    private static Map<String, Object> deepMap(JsonObject obj) {
+        try {
+            return JSONObjectUtils.parse(obj.encode());
+        } catch (ParseException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+}
diff --git a/components/camel-oauth/src/main/resources/log4j2.properties b/components/camel-oauth/src/main/resources/log4j2.properties
new file mode 100644
index 00000000000..c755a6b14ed
--- /dev/null
+++ b/components/camel-oauth/src/main/resources/log4j2.properties
@@ -0,0 +1,28 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+
+# console logger
+appender.stdout.type = Console
+appender.stdout.name = out
+appender.stdout.layout.type = PatternLayout
+# logging style that is similar to spring boot
+appender.stdout.layout.pattern = %style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{Dim} %highlight{%5p} %style{%pid}{Magenta} %style{---}{Dim} %style{[%15.15t]}{Dim} %style{%-40.40c}{Cyan} %style{:}{Dim} %m%n
+
+# log to console
+rootLogger = INFO,out
+rootLogger.level = INFO
+
diff --git a/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/AbstractKeycloakTest.java b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/AbstractKeycloakTest.java
new file mode 100644
index 00000000000..a5d0bab44c2
--- /dev/null
+++ b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/AbstractKeycloakTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.oauth;
+
+import org.junit.jupiter.api.Assumptions;
+
+import static org.apache.camel.test.oauth.KeycloakAdmin.AdminParams;
+import static org.apache.camel.test.oauth.KeycloakAdmin.ClientParams;
+import static org.apache.camel.test.oauth.KeycloakAdmin.RealmParams;
+import static org.apache.camel.test.oauth.KeycloakAdmin.UserParams;
+
+abstract class AbstractKeycloakTest {
+
+    static final int port = 8080; // AvailablePortFinder.getNextAvailable();
+
+    static final String APP_BASE_URL = "http://127.0.0.1:" + port + "/";
+    static final String KEYCLOAK_BASE_URL = "https://keycloak.local:30443/";
+    static final String TEST_REALM = "camel";
+    static final String TEST_CLIENT_ID = "camel-client";
+    static final String TEST_CLIENT_SECRET = "camel-client-secret";
+
+    static KeycloakAdmin admin;
+    static boolean removeRealm;
+
+    static void setupKeycloakRealm() throws Exception {
+
+        admin = new KeycloakAdmin(new AdminParams(KEYCLOAK_BASE_URL));
+        Assumptions.assumeTrue(admin.isKeycloakRunning(), "Keycloak is not running");
+
+        // Setup Keycloak realm, client, user
+        //
+        if (!admin.realmExists(TEST_REALM)) {
+            admin.withRealm(new RealmParams(TEST_REALM))
+                    .withUser(new UserParams("alice")
+                            .setEmail("alice@example.com")
+                            .setFirstName("Alice")
+                            .setLastName("Brown"))
+                    .withClient(new ClientParams(TEST_CLIENT_ID)
+                            .setClientSecret(TEST_CLIENT_SECRET)
+                            .setLogoutRedirectUri(APP_BASE_URL)
+                            .setServiceAccountsEnabled(true)
+                            .setRedirectUri(APP_BASE_URL + "auth"));
+            removeRealm = true;
+        }
+    }
+
+    static void removeKeycloakRealm() {
+        if (admin != null && removeRealm) {
+            admin.removeRealm().close();
+        }
+    }
+}
diff --git a/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/KeycloakAdmin.java b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/KeycloakAdmin.java
new file mode 100644
index 00000000000..d2f8f2b5031
--- /dev/null
+++ b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/KeycloakAdmin.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.oauth;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.KeycloakBuilder;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
+class KeycloakAdmin {
+
+    private static Keycloak keycloak;
+    private String lastRealm;
+
+    public KeycloakAdmin(AdminParams params) {
+        keycloak = KeycloakBuilder.builder()
+                .serverUrl(params.serverUrl)
+                .realm(params.realm)
+                .clientId(params.clientId)
+                .grantType(OAuth2Constants.PASSWORD)
+                .username(params.username)
+                .password(params.password)
+                .build();
+    }
+
+    public Keycloak getKeycloak() {
+        return keycloak;
+    }
+
+    public KeycloakAdmin withRealm(RealmParams params) {
+        RealmRepresentation realm = new RealmRepresentation();
+        realm.setRealm(params.realm);
+        realm.setEnabled(true);
+        getKeycloak().realms().create(realm);
+        this.lastRealm = params.realm;
+        return this;
+    }
+
+    public KeycloakAdmin withClient(ClientParams params) {
+        List<String> redirectUris = new ArrayList<>(List.of(params.redirectUri));
+        if (params.logoutRedirectUri != null) {
+            redirectUris.add(params.logoutRedirectUri);
+        }
+        ClientRepresentation client = new ClientRepresentation();
+        client.setClientId(params.clientId);
+        client.setSecret(params.clientSecret);
+        client.setPublicClient(params.publicClient);
+        client.setServiceAccountsEnabled(params.serviceAccounts);
+        client.setRedirectUris(redirectUris);
+        client.setEnabled(true);
+        getKeycloak().realm(lastRealm).clients().create(client);
+        return this;
+    }
+
+    public KeycloakAdmin withUser(UserParams params) {
+
+        if (params.password == null) {
+            params.password = params.username;
+        }
+
+        UsersResource users = getKeycloak().realm(lastRealm).users();
+
+        UserRepresentation user = new UserRepresentation();
+        user.setUsername(params.username);
+        user.setEmail(params.email);
+        user.setEmailVerified(true);
+        user.setFirstName(params.firstName);
+        user.setLastName(params.lastName);
+        user.setEnabled(true);
+        users.create(user);
+
+        String userId = users.search(params.username).get(0).getId();
+
+        CredentialRepresentation password = new CredentialRepresentation();
+        password.setType(CredentialRepresentation.PASSWORD);
+        password.setValue(params.password);
+        password.setTemporary(false);
+        users.get(userId).resetPassword(password);
+
+        return this;
+    }
+
+    public KeycloakAdmin removeRealm() {
+        if (lastRealm != null) {
+            getKeycloak().realm(lastRealm).remove();
+            lastRealm = null;
+        }
+        return this;
+    }
+
+    public void close() {
+        getKeycloak().close();
+        keycloak = null;
+    }
+
+    public boolean isKeycloakRunning() {
+        try {
+            var serverInfo = getKeycloak().serverInfo().getInfo();
+            return serverInfo != null;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    public boolean realmExists(String realm) {
+        try {
+            var realmRepresentation = getKeycloak().realm(realm).toRepresentation();
+            return realmRepresentation != null;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    public static class AdminParams {
+        String serverUrl;
+        String realm = "master";
+        String clientId = "admin-cli";
+        String username = "admin";
+        String password = "admin";
+
+        public AdminParams(String serverUrl) {
+            this.serverUrl = serverUrl;
+        }
+
+        public AdminParams setUsername(String username) {
+            this.username = username;
+            return this;
+        }
+
+        public AdminParams setPassword(String password) {
+            this.password = password;
+            return this;
+        }
+    }
+
+    public static class RealmParams {
+        String realm;
+
+        public RealmParams(String realm) {
+            this.realm = realm;
+        }
+    }
+
+    public static class ClientParams {
+        String clientId;
+        String clientSecret;
+        String redirectUri;
+        String logoutRedirectUri;
+        boolean publicClient;
+        boolean serviceAccounts;
+
+        public ClientParams(String clientId) {
+            this.clientId = clientId;
+        }
+
+        public ClientParams setClientSecret(String clientSecret) {
+            this.clientSecret = clientSecret;
+            return this;
+        }
+
+        public ClientParams setPublicClient(boolean enabled) {
+            this.publicClient = enabled;
+            return this;
+        }
+
+        public ClientParams setRedirectUri(String redirectUri) {
+            this.redirectUri = redirectUri;
+            return this;
+        }
+
+        public ClientParams setLogoutRedirectUri(String redirectUri) {
+            this.logoutRedirectUri = redirectUri;
+            return this;
+        }
+
+        public ClientParams setServiceAccountsEnabled(boolean enabled) {
+            this.serviceAccounts = enabled;
+            return this;
+        }
+    }
+
+    public static class UserParams {
+        String username;
+        String password;
+        String email;
+        String firstName;
+        String lastName;
+
+        public UserParams(String username) {
+            this.username = username;
+        }
+
+        public UserParams setPassword(String password) {
+            this.password = password;
+            return this;
+        }
+
+        public UserParams setEmail(String email) {
+            this.email = email;
+            return this;
+        }
+
+        public UserParams setFirstName(String firstName) {
+            this.firstName = firstName;
+            return this;
+        }
+
+        public UserParams setLastName(String lastName) {
+            this.lastName = lastName;
+            return this;
+        }
+    }
+}
diff --git a/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/KeycloakOAuthRestTest.java b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/KeycloakOAuthRestTest.java
new file mode 100644
index 00000000000..264463b05f1
--- /dev/null
+++ b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/KeycloakOAuthRestTest.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.oauth;
+
+import java.util.Map;
+
+import com.nimbusds.jose.util.JSONObjectUtils;
+import org.apache.camel.CamelContext;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.platform.http.main.MainHttpServer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.model.dataformat.JsonLibrary;
+import org.apache.camel.oauth.OAuthBearerTokenProcessor;
+import org.apache.camel.oauth.OAuthClientCredentialsProcessor;
+import org.apache.camel.spi.PropertiesComponent;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_CLIENT_ID;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_CLIENT_SECRET;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_PROVIDER_BASE_URI;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_SESSION_ID;
+
+class KeycloakOAuthRestTest extends AbstractKeycloakTest {
+
+    private static CamelContext camelContext;
+
+    @BeforeAll
+    static void setUp() throws Exception {
+
+        setupKeycloakRealm();
+
+        camelContext = new DefaultCamelContext();
+        camelContext.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+
+                // Expose a REST endpoint on platform-http
+                restConfiguration().component("platform-http");
+
+                rest("/provider")
+                        .get("/data")
+                        .to("direct:processData");
+
+                from("direct:processData")
+                        .process(new OAuthBearerTokenProcessor())
+                        .setBody(exc -> Map.of("msg", "Hello " + exc.getMessage().getBody(String.class)))
+                        .marshal().json(JsonLibrary.Jackson)
+                        .setHeader("Content-Type", constant("application/json"));
+
+                from("direct:start")
+                        .process(new OAuthClientCredentialsProcessor())
+                        .setHeader("CamelHttpMethod", constant("GET"))
+                        .to("vertx-http:http://127.0.0.1:" + port + "/provider/data")
+                        .log("Response: ${body}");
+            }
+        });
+
+        PropertiesComponent props = camelContext.getPropertiesComponent();
+        props.addInitialProperty(CAMEL_OAUTH_PROVIDER_BASE_URI, KEYCLOAK_BASE_URL + "realms/" + TEST_REALM);
+        props.addInitialProperty(CAMEL_OAUTH_CLIENT_ID, TEST_CLIENT_ID);
+        props.addInitialProperty(CAMEL_OAUTH_CLIENT_SECRET, TEST_CLIENT_SECRET);
+
+        MainHttpServer httpServer = new MainHttpServer();
+        httpServer.setPort(port);
+
+        camelContext.addService(httpServer);
+        camelContext.start();
+    }
+
+    @AfterAll
+    static void tearDown() {
+        if (camelContext != null) {
+            camelContext.stop();
+        }
+        removeKeycloakRealm();
+    }
+
+    @Test
+    void testClientCredentialsAuth() throws Exception {
+
+        // Verify Realm, Client, and User exist
+        var keycloak = admin.getKeycloak();
+        Assertions.assertNotNull(keycloak.realm(TEST_REALM).toRepresentation());
+        Assertions.assertEquals(1, keycloak.realm(TEST_REALM).clients().findByClientId(TEST_CLIENT_ID).size());
+        Assertions.assertEquals(1, keycloak.realm(TEST_REALM).users().search("alice").size());
+
+        System.out.println("✅ Keycloak realm, client, and user created successfully!");
+
+        ProducerTemplate template = camelContext.createProducerTemplate();
+        var res = template.request("direct:start", exc -> exc.getIn().setBody("Kermit"));
+        var msg = res.getMessage();
+
+        var body = msg.getBody(String.class);
+        Assertions.assertEquals("Hello Kermit", JSONObjectUtils.parse(body).get("msg"));
+
+        // Assert that the CamelOAuthSessionId is present
+        var sessionId = msg.getHeader(CAMEL_OAUTH_SESSION_ID, String.class);
+        Assertions.assertNotNull(sessionId);
+
+        // Set the CamelOAuthSessionId on the second run, which should shortcut
+        // client credential authentication with much faster token introspection
+        body = template.requestBodyAndHeader("direct:start", "Kermit", CAMEL_OAUTH_SESSION_ID, sessionId, String.class);
+        Assertions.assertEquals("Hello Kermit", JSONObjectUtils.parse(body).get("msg"));
+    }
+}
diff --git a/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/KeycloakOAuthWebAppTest.java b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/KeycloakOAuthWebAppTest.java
new file mode 100644
index 00000000000..6ee1fae7bc7
--- /dev/null
+++ b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/KeycloakOAuthWebAppTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.oauth;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.platform.http.main.MainHttpServer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.oauth.OAuthCodeFlowCallbackProcessor;
+import org.apache.camel.oauth.OAuthCodeFlowProcessor;
+import org.apache.camel.oauth.OAuthLogoutProcessor;
+import org.apache.camel.spi.PropertiesComponent;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_CLIENT_ID;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_CLIENT_SECRET;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_LOGOUT_REDIRECT_URI;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_PROVIDER_BASE_URI;
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_REDIRECT_URI;
+
+class KeycloakOAuthWebAppTest extends AbstractKeycloakTest {
+
+    private static CamelContext camelContext;
+
+    @BeforeAll
+    static void setUp() throws Exception {
+
+        setupKeycloakRealm();
+
+        camelContext = new DefaultCamelContext();
+        camelContext.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("platform-http:/")
+                        .setBody(simple("resource:classpath:index.html"));
+                from("platform-http:/static/styles.css")
+                        .setBody(simple("resource:classpath:styles.css"));
+                from("platform-http:/auth")
+                        .process(new OAuthCodeFlowCallbackProcessor());
+                from("platform-http:/protected")
+                        .process(new OAuthCodeFlowProcessor())
+                        .setBody(simple("resource:classpath:protected.html"));
+                from("platform-http:/logout")
+                        .process(new OAuthLogoutProcessor())
+                        .process(exc -> exc.getContext().getGlobalOptions().put("OAuthLogout", "ok"));
+            }
+        });
+
+        PropertiesComponent props = camelContext.getPropertiesComponent();
+        props.addInitialProperty(CAMEL_OAUTH_PROVIDER_BASE_URI, KEYCLOAK_BASE_URL + "realms/" + TEST_REALM);
+        props.addInitialProperty(CAMEL_OAUTH_REDIRECT_URI, APP_BASE_URL + "auth");
+        props.addInitialProperty(CAMEL_OAUTH_CLIENT_ID, TEST_CLIENT_ID);
+        props.addInitialProperty(CAMEL_OAUTH_CLIENT_SECRET, TEST_CLIENT_SECRET);
+        props.addInitialProperty(CAMEL_OAUTH_LOGOUT_REDIRECT_URI, APP_BASE_URL);
+
+        MainHttpServer httpServer = new MainHttpServer();
+        httpServer.setPort(port);
+
+        camelContext.addService(httpServer);
+        camelContext.start();
+    }
+
+    @AfterAll
+    static void tearDown() {
+        if (camelContext != null) {
+            camelContext.stop();
+        }
+        removeKeycloakRealm();
+    }
+
+    @Test
+    void testCodeFlowAuth() throws Exception {
+
+        // Verify Realm, Client, and User exist
+        var keycloak = admin.getKeycloak();
+        Assertions.assertNotNull(keycloak.realm(TEST_REALM).toRepresentation());
+        Assertions.assertEquals(1, keycloak.realm(TEST_REALM).clients().findByClientId(TEST_CLIENT_ID).size());
+        Assertions.assertEquals(1, keycloak.realm(TEST_REALM).users().search("alice").size());
+
+        System.out.println("✅ Keycloak realm, client, and user created successfully!");
+        System.out.println("✅ Open: " + APP_BASE_URL);
+
+        // Open WebApp in Browser (works on macOS)
+        // Runtime.getRuntime().exec("open " + APP_BASE_URL);
+
+        // Increase for manual testing
+        int maxLoopCount = 10;
+
+        for (int i = maxLoopCount; i > 0; i--) { // timeout after 20sec
+            var options = camelContext.getGlobalOptions();
+            if ("ok".equals(options.get("OAuthLogout"))) {
+                System.out.println("✅ OAuthLogout - ok");
+                i = 0;
+            }
+            if (i % 4 == 0) {
+                System.out.printf("Waiting on logout: %d/%d - %s%n", maxLoopCount - i, maxLoopCount, APP_BASE_URL + "logout");
+            }
+            Thread.sleep(500L);
+        }
+    }
+}
diff --git a/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/SSLCertTrustTest.java b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/SSLCertTrustTest.java
new file mode 100644
index 00000000000..d15b569438f
--- /dev/null
+++ b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/SSLCertTrustTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.oauth;
+
+import java.io.IOException;
+import java.net.URL;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLHandshakeException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.Test;
+
+class SSLCertTrustTest {
+
+    private static final String KEYCLOAK_SERVER_URL = "https://keycloak.local:30443/";
+
+    @Test
+    void testTrustedCertificate() {
+
+        var admin = new KeycloakAdmin(new KeycloakAdmin.AdminParams(KEYCLOAK_SERVER_URL));
+        Assumptions.assumeTrue(admin.isKeycloakRunning(), "Keycloak is not running");
+
+        Assertions.assertDoesNotThrow(() -> connectToUrl(KEYCLOAK_SERVER_URL), "Certificate should be trusted");
+    }
+
+    @Test
+    void testUntrustedCertificate() {
+        String url = "https://untrusted-root.badssl.com"; // Example of an untrusted cert
+        Assertions.assertThrows(SSLHandshakeException.class, () -> connectToUrl(url), "Certificate should not be trusted");
+    }
+
+    private static void connectToUrl(String httpsUrl) throws IOException {
+        URL url = new URL(httpsUrl);
+        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
+        conn.connect();
+    }
+}
diff --git a/components/camel-oauth/src/test/resources/index.html b/components/camel-oauth/src/test/resources/index.html
new file mode 100644
index 00000000000..8f75dcc4f1f
--- /dev/null
+++ b/components/camel-oauth/src/test/resources/index.html
@@ -0,0 +1,35 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Secured Web App</title>
+    <link rel="stylesheet" href="/static/styles.css"/>
+</head>
+<body>
+<h1>Web App</h1>
+<p>All users can see this.</p>
+<a href="/">Home</a>
+<a href="/protected">Protected</a>
+<a href="https://keycloak.local:30443/" target="_blank">Keycloak</a>
+<a href="/logout">Logout</a>
+</body>
+</html>
diff --git a/components/camel-oauth/src/test/resources/protected.html b/components/camel-oauth/src/test/resources/protected.html
new file mode 100644
index 00000000000..323118b076b
--- /dev/null
+++ b/components/camel-oauth/src/test/resources/protected.html
@@ -0,0 +1,35 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Secured Web App</title>
+    <link rel="stylesheet" href="/static/styles.css"/>
+</head>
+<body>
+<h1>Secured Web App</h1>
+<p>Only authenticated users can see this.</p>
+<a href="/">Home</a>
+<a href="/protected">Protected</a>
+<a href="https://keycloak.local:30443/" target="_blank">Keycloak</a>
+<a href="/logout">Logout</a>
+</body>
+</html>
diff --git a/components/camel-oauth/src/test/resources/styles.css b/components/camel-oauth/src/test/resources/styles.css
new file mode 100644
index 00000000000..85c711b2819
--- /dev/null
+++ b/components/camel-oauth/src/test/resources/styles.css
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+body {
+    font-family: Arial, sans-serif;
+    text-align: center;
+    padding: 20px;
+}
+h1 {
+    color: green;
+}
diff --git a/components/pom.xml b/components/pom.xml
index ad1a2ec9954..11c21eeb47d 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -229,6 +229,7 @@
         <module>camel-netty</module>
         <module>camel-nitrite</module>
         <module>camel-oaipmh</module>
+        <module>camel-oauth</module>
         <module>camel-observation</module>
         <module>camel-ognl</module>
         <module>camel-olingo2</module>
diff --git a/docs/components/modules/others/examples/json/oauth.json b/docs/components/modules/others/examples/json/oauth.json
new file mode 120000
index 00000000000..a8128aa2ac7
--- /dev/null
+++ b/docs/components/modules/others/examples/json/oauth.json
@@ -0,0 +1 @@
+../../../../../../components/camel-oauth/src/generated/resources/oauth.json
\ No newline at end of file
diff --git a/parent/pom.xml b/parent/pom.xml
index e03c07ec56a..eb65be046fc 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -1976,6 +1976,11 @@
                 <artifactId>camel-oaipmh</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.camel</groupId>
+                <artifactId>camel-oauth</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.camel</groupId>
                 <artifactId>camel-observability-services</artifactId>