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>