Oauth
Since Camel 4.12
The camel-oauth module comes with Processors that can be added to a route on the client and resource owner side. These processors intercept the message flow and perform the necessary authentication steps against an Identity Provider (IdP) in some specs it also called Authorization Server. Our primary choice of IdP is Keycloak
The idea is that a "Resource Owner" can give a "User Agent" access to some protected resources without sharing credentials directly with the agent.
For example, Alice has an account with Spotify and now wishes to use a cool service from Acme which compiles a daily playlist according based on Alice’s preferences. Instead of giving Acme her Spotify credentials (i.e. username/password) directly, Acme can obtain an access token from an Identity Provider that encodes the scope and duration for Acme to access Alice’s Spotify account. Alice can revoke access any time - Acme never sees more information than what Alice has granted and is necessary to perform the wanted service.
Maven users will need to add the following dependency to their pom.xml for this component:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-oauth</artifactId>
<version>x.x.x</version>
<!-- use the same version as your Camel core version -->
</dependency> Authentication/Authorization Flow Types
OIDC Authorization Code Flow
The Authorization Code Flow returns an Authorization Code to the Client, which can then exchange it for an ID Token and an Access Token directly. The Authorization Code flow is suitable for Clients that can securely maintain a Client Secret between themselves and the Authorization Server.
This code flow relies on user interaction with a browser based application. It is not suitable for fully automated authorization for example in the case of REST based service interaction.
For details see the OIDC 1.0 spec.
Configuration Properties
| Name | Description |
|---|---|
| The base URL to the identity provider (e.g. https://oauth.localtest.me/kc/realms/camel) |
| Valid URI pattern a browser can redirect to after a successful login (e.g. http://127.0.0.1:8080/auth). Must be registered with the identity provider. |
| The client identifier registered with the identity provider. |
| The client secret provided by the identity provider. |
| (Optional) Valid URI pattern a browser can redirect to after a successful logout. Can be registered with the identity provider. |
Client Credentials Grant
A client can request an access token using only the client id and secret shared with the identity provider.
This flow is suitable for fully automated authorization, for example in the case of REST based service interaction.
For details see the OAuth 2.0 spec.
Configuration Properties
| Name | Description |
|---|---|
| The base URL to the identity provider (e.g. https://oauth.localtest.me/kc/realms/camel) |
| The client identifier registered with the identity provider. |
| The client secret provided by the identity provider. |
OAuth SPI for Component Integration
The camel-oauth module provides an SPI (OAuthClientAuthenticationFactory) that allows other Camel components to use OAuth Client Credentials authentication without a compile-time dependency on camel-oauth. Components discover the factory at runtime via Camel’s FactoryFinder mechanism. If camel-oauth is not on the classpath, a clear error message instructs the user to add it.
Components that support this integration include: camel-openai, camel-huggingface, camel-docling and camel-ibm-watsonx-ai. Each of these components exposes an oauthProfile parameter.
Named Profiles (Multiple Identity Providers)
Named profiles allow configuring multiple identity providers in a single application. Each profile is identified by a name and has its own set of properties.
Configuration Properties
Properties are resolved from camel.oauth.<profileName>.*:
| Name | Required | Description |
|---|---|---|
| Yes | The client identifier registered with the identity provider. |
| Yes | The client secret provided by the identity provider. |
| Yes | The OAuth 2.0 token endpoint URL. |
| No | The OAuth 2.0 scope to request. |
| No | Whether to cache tokens. Default: |
| No | Default token expiry if |
| No | Safety margin subtracted from token expiry to refresh early. Default: |
Example: Multiple Identity Providers
# Keycloak for backend services
camel.oauth.keycloak.client-id=backend-client
camel.oauth.keycloak.client-secret=backend-secret
camel.oauth.keycloak.token-endpoint=https://keycloak.example.com/realms/main/protocol/openid-connect/token
# Azure AD for OpenAI
camel.oauth.azure.client-id=openai-client
camel.oauth.azure.client-secret=openai-secret
camel.oauth.azure.token-endpoint=https://login.microsoftonline.com/tenant/oauth2/v2.0/token
camel.oauth.azure.scope=https://cognitiveservices.azure.com/.default Components reference a profile by name via the oauthProfile parameter:
- route:
from:
uri: "direct:start"
steps:
- to:
uri: "openai:chat-completion"
parameters:
model: "gpt-4"
oauthProfile: "azure" Incoming Bearer Token Validation
The camel-oauth module also provides an OAuthTokenValidationFactory SPI for validating incoming Authorization: Bearer tokens. Components such as Platform HTTP can use this SPI without a compile-time dependency on camel-oauth. This module is the default provider for the SPI; runtime-specific integrations can provide their own implementation backed by their native security stack.
JWT tokens are validated locally with JWKS. The validator verifies the JWS signature, checks exp and nbf, and validates iss and aud. Opaque tokens are validated with RFC 7662 introspection using a strict bounded HTTP/JSON implementation.
Incoming Token Validation Properties
Properties are resolved from camel.oauth.<profileName>.*:
| Name | Required | Description |
|---|---|---|
| No | Base URL used to discover OIDC metadata from |
| For JWT if | JWKS URL used to validate JWT signatures. |
| For opaque tokens if | RFC 7662 introspection endpoint used to validate opaque tokens. |
| For opaque tokens | Client identifier for introspection. If omitted, |
| For opaque tokens | Client secret for introspection. If omitted, |
| No | Fallback client identifier used by introspection when |
| No | Fallback client secret used by introspection when |
| Yes, unless | Expected |
| Yes, unless | Comma-separated accepted |
| No | Expected JWT |
| No | Clock skew leeway for temporal claims. Default: |
| No | JWKS cache TTL. Default: |
| No | OIDC discovery metadata cache TTL. Default: |
| No | Connect timeout for OIDC discovery, JWKS fetch, and introspection calls. Default: |
| No | Read timeout for OIDC discovery, JWKS fetch, and introspection calls. Default: |
| No | Whether JWT tokens must include an |
| No | Comma-separated JWS algorithm allowlist, for example |
| No | Set to |
| No | Set to |
| No | Set to |
Choosing JWT Validation, OIDC Discovery, or Opaque Introspection
Camel chooses the validation path from the incoming token and the configured profile:
-
If only
introspection-endpointis configured, Camel introspects the token, regardless of token shape. -
If only
jwks-endpointis configured, Camel validates the token as a JWT. -
If both JWKS and introspection are configured, parseable signed JWT tokens are validated locally and other tokens are introspected. If a signed JWT fails local validation, Camel rejects it and does not fall back to introspection.
Local JWT validation avoids a per-request identity-provider call and is appropriate when the issuer publishes signing keys through JWKS. Opaque introspection performs a blocking outbound HTTP call for each validation and is appropriate when the issuer does not expose token contents to the resource server. For production resource-server routes, configure expected-issuer and expected-audience and use HTTPS endpoints.
JWT Validation with an Explicit JWKS Endpoint
camel.oauth.myprofile.jwks-endpoint=https://idp.example.com/.well-known/jwks.json
camel.oauth.myprofile.expected-issuer=https://idp.example.com
camel.oauth.myprofile.expected-audience=my-api
camel.oauth.myprofile.connect-timeout-seconds=5
camel.oauth.myprofile.read-timeout-seconds=10 OIDC Discovery
Use base-uri when the identity provider exposes OIDC metadata at /.well-known/openid-configuration. Camel discovers jwks_uri when it is not configured explicitly. Camel discovers and enables introspection_endpoint only when introspection credentials are also configured. This lets JWT-only resource servers use OIDC discovery against providers that advertise introspection metadata without requiring unused client credentials. When expected-issuer is not set, the configured base-uri is used as the expected issuer. The discovery document issuer must match the configured expected issuer. When using the programmatic OAuthTokenValidationConfig.setOidcDiscoveryUrl() API directly without expected-issuer, the discovery URL must use the standard /.well-known/openid-configuration path so Camel can derive and validate the expected issuer. For non-standard discovery URLs, configure expected-issuer explicitly.
camel.oauth.myprofile.base-uri=https://idp.example.com/realms/main
camel.oauth.myprofile.expected-audience=my-api,my-api-v2
camel.oauth.myprofile.connect-timeout-seconds=5
camel.oauth.myprofile.read-timeout-seconds=10 Opaque Token Introspection
camel.oauth.myprofile.introspection-endpoint=https://idp.example.com/oauth2/introspect
camel.oauth.myprofile.introspection-client-id=resource-server-client
camel.oauth.myprofile.introspection-client-secret=resource-server-secret
camel.oauth.myprofile.expected-issuer=https://idp.example.com
camel.oauth.myprofile.expected-audience=my-api
camel.oauth.myprofile.connect-timeout-seconds=5
camel.oauth.myprofile.read-timeout-seconds=10 The introspection client secret is sensitive. Store it with the same secret-management mechanism used for other Camel secrets.
Mixed JWT and Opaque Tokens
camel.oauth.myprofile.jwks-endpoint=https://idp.example.com/.well-known/jwks.json
camel.oauth.myprofile.introspection-endpoint=https://idp.example.com/oauth2/introspect
camel.oauth.myprofile.introspection-client-id=resource-server-client
camel.oauth.myprofile.introspection-client-secret=resource-server-secret
camel.oauth.myprofile.expected-issuer=https://idp.example.com
camel.oauth.myprofile.expected-audience=my-api
camel.oauth.myprofile.connect-timeout-seconds=5
camel.oauth.myprofile.read-timeout-seconds=10 To use a runtime-specific or application-specific validator for one profile, bind an OAuthTokenValidationFactory bean and reference it from the profile:
camel.oauth.myprofile.validation-factory=#bean:myTokenValidationFactory For the default unnamed profile, use camel.oauth.validation-factory.
If this property is configured, the referenced bean must exist and implement OAuthTokenValidationFactory; otherwise the route fails to start.
Use HTTPS endpoints for OIDC discovery, JWKS, and introspection in production. Plain HTTP endpoints are rejected by default; set allow-insecure-http=true only for local testing or other trusted development environments.
Opaque-token introspection performs a blocking outbound HTTP call for each token validation, so tune the timeout properties for the expected request rate and identity-provider latency.
Validation error details are intended for logs and diagnostics. Do not return OAuthTokenValidationResult.getError() directly to external clients; HTTP responses should use generic messages such as Unauthorized. Stable error categories are available through OAuthTokenValidationResult.getErrorCode(): INVALID_TOKEN, INACTIVE_TOKEN, EXPIRED_TOKEN, NOT_YET_VALID, MISSING_EXPIRATION, INVALID_SIGNATURE, NO_MATCHING_KEY, UNSUPPORTED_ALGORITHM, INVALID_ISSUER, and INVALID_AUDIENCE.
Using OAuth Programmatically
The OAuthHelper utility from camel-support can be used for both outgoing client credentials token acquisition and incoming bearer token validation.
Validate an Incoming Bearer Token
import org.apache.camel.spi.OAuthTokenValidationResult;
import org.apache.camel.support.OAuthHelper;
OAuthTokenValidationResult result;
try {
result = OAuthHelper.validateOAuthToken(camelContext, "myprofile", bearerToken);
} catch (RuntimeException e) {
// Treat configuration, network, and identity-provider failures as infrastructure errors.
throw new IllegalStateException("Token validation is unavailable", e);
}
if (!result.isValid()) {
OAuthTokenValidationResult.ErrorCode errorCode = result.getErrorCode();
// Use generic responses at HTTP boundaries. Do not expose result.getError() to external clients.
throw new IllegalArgumentException("Unauthorized");
}
String subject = result.getSubject();
String principalName = result.getName();
boolean canRead = result.hasScope("read");
String email = result.getClaim("email", String.class); For the default unnamed profile, use:
OAuthTokenValidationResult result = OAuthHelper.validateOAuthToken(camelContext, bearerToken); For advanced validation use cases, resolve OAuthTokenValidationFactory directly and pass an explicit OAuthTokenValidationConfig.
import org.apache.camel.spi.OAuthTokenValidationConfig;
import org.apache.camel.spi.OAuthTokenValidationFactory;
import org.apache.camel.spi.OAuthTokenValidationResult;
import org.apache.camel.support.OAuthHelper;
OAuthTokenValidationFactory factory = OAuthHelper.resolveOAuthTokenValidationFactory(camelContext, "myprofile");
OAuthTokenValidationConfig config = new OAuthTokenValidationConfig()
.setJwksEndpoint("https://idp.example.com/.well-known/jwks.json")
.setExpectedIssuer("https://idp.example.com")
.setExpectedAudience("my-api");
OAuthTokenValidationResult result = factory.validateToken(config, bearerToken); Resolve an Outgoing Client Credentials Token
import org.apache.camel.support.OAuthHelper;
String token = OAuthHelper.resolveOAuthToken(camelContext, "keycloak"); For advanced use cases, the OAuthClientAuthenticationFactory can be used directly with an explicit OAuthClientConfig:
import org.apache.camel.spi.OAuthClientAuthenticationFactory;
import org.apache.camel.spi.OAuthClientConfig;
import org.apache.camel.support.ResolverHelper;
OAuthClientAuthenticationFactory factory = ResolverHelper.resolveMandatoryService(
context, OAuthClientAuthenticationFactory.FACTORY,
OAuthClientAuthenticationFactory.class, "camel-oauth");
String token = factory.resolveToken(
new OAuthClientConfig()
.setClientId("my-client")
.setClientSecret("my-secret")
.setTokenEndpoint("https://idp.example.com/token")); Trusted Certificates
Naturally, we want all communication between camel and the identity provider to be secured at the transport layer (TLS). For this, the Camel service need’s to trust the identity provider’s certificate.
# Fetch the certificate from the IdP endpoint
openssl s_client -connect oauth.localtest.me:443 | openssl x509 > cluster.crt
# Import certificate to Java Keystore (i.e. trust the certificate)
sudo keytool -import -alias keycloak -file cluster.crt -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit
# Trust this cert on macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cluster.crt
# Trust this cert on Linux
sudo cp cluster.crt /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust OAuth for Kafka
For Kafka we can use strimzi-kafka-oauth directly, for example like this …
Supported Runtimes
Camel OAuth is supported in all Camel Runtimes
-
camel-main
-
spring-boot
-
quarkus
Specifically, it provides an abstraction for the various http-platforms that are native to these runtimes.
Supported Cluster Environments
Camel applications requiring OAuth authentication are likely part of a larger more complex system architecture, which also likely are part of some larger Kubernetes cluster deployment. In our examples we support these Kubernetes environments …
-
Local Cluster (e.g. DockerDesktop Kubernetes)
-
Remote K3S Cluster
-
Red Hat OpenShift
As part of this project we provide a set of Helm charts that install the required infrastructure components for the respective cluster environment. For details, have a look at the dedicated readme.
Keycloak is already configured in such a way that below examples should run without further ado.
Camel OAuth Examples
There is a comprehensive set of camel-oauth examples as part of camel-cloud-examples. You’ll find camel-jbang kubernetes examples for every OAuth flow, for every runtime, on every supported cluster.
For example in the following makefile:
k8s-fetch-cert:
@mkdir -p tls
@echo -n | openssl s_client -connect oauth.localtest.me:443 | openssl x509 > tls/cluster.crt
k8s-export: k8s-fetch-cert
@$(CAMEL_CMD) kubernetes export platform-http-files/* tls/* \
--dep=org.apache.camel:camel-oauth:4.21.0-SNAPSHOT \
--gav=examples:platform-http-oauth:1.0.0 \
--property=camel.oauth.base-uri=https://oauth.localtest.me/kc/realms/camel \
--property=camel.oauth.redirect-uri=http://127.0.0.1:8080/auth \
--property=camel.oauth.logout.redirect-uri=http://127.0.0.1:8080/ \
--property=camel.oauth.client-id=camel-client \
--property=camel.oauth.client-secret=camel-client-secret \
--property=ssl.truststore.certificates=tls/cluster.crt \
--ignore-loading-error=true \
--image-builder=docker \
--image-push=false \
--trait container.image-pull-policy=IfNotPresent \
--runtime=camel-main