Authentication and authorization
The camel-auth crate validates bearer tokens, maps claims into a
Principal, and evaluates authorization decisions for route-level
security_policy. It is provider-neutral. OIDC presets for specific
providers live in component crates such as camel-component-keycloak.
Architecture
The auth pipeline has three layers:
- TokenAuthenticator validates a bearer or API token and returns a
Principal. Implementations includeIntrospectionAuthenticator(RFC 7662),ApiKeyAuthenticator,StaticTokenAuthenticator, andLocalJwtValidator. - ClaimsMapper maps token or introspection claims into
Principalfields: subject, roles, scopes, issuer, audience.JsonPointerClaimsMapperresolves JSON Pointer paths, so any OIDC provider works without code. - PermissionEvaluator evaluates resource, action, and scope requests and returns a
PermissionDecision. Route-levelsecurity_policy.permissioncalls it.
The enforcement boundary is SecurityPolicyLayer in camel-core. It evaluates BEFORE route steps run. A granted decision stores Principal properties on the Exchange. A denied decision returns Unauthorized into route error handling.
See ADR-0010 for the pre-pipeline authorization decision.
Keycloak-style auth
The native auth pipeline reproduces a Keycloak flow without external dependencies. It issues JWTs, validates them, and applies role-based policies. The same pipeline works with a real Keycloak through camel-component-keycloak.
Issue tokens with NativeTokenIssuer:
let alice_token = token_issuer
.issue_token("alice", "alice-secret", Some("read write"), None)
.await?;
let bob_token = token_issuer
.issue_token("bob", "bob-secret", Some("read"), None)
.await?;
Validate tokens with LocalJwtValidator and NativeJwksProvider:
println!("--- JWT Validation ---");
let alice_principal = validator
.authenticate_bearer(&alice_token.access_token)
.await;
match &alice_principal {
Ok(p) => println!("Alice OK subject={} roles={:?}", p.subject, p.roles), // allow-secret
Err(e) => println!("Alice invalid ({e})"), // allow-secret
}
let bob_principal = validator.authenticate_bearer(&bob_token.access_token).await;
match &bob_principal {
Ok(p) => println!(
"Bob JWT: VALID (subject={}, roles={:?})",
p.subject, p.roles
),
Err(e) => println!("Bob JWT: INVALID ({e})"),
}
Apply a RolePolicy that checks for required roles:
println!("--- Role-Based Security Policy ---");
let admin_policy: Arc<dyn SecurityPolicy> = Arc::new(RolePolicy::new(
vec!["admin".to_string()],
true,
false,
validator.clone(),
));
let mut alice_exchange = Exchange::default();
alice_exchange.input.headers.insert(
"authorization".to_string(),
Value::String(format!("Bearer {}", *alice_token.access_token)), // allow-secret
);
let mut bob_exchange = Exchange::default();
bob_exchange.input.headers.insert(
"authorization".to_string(),
Value::String(format!("Bearer {}", *bob_token.access_token)), // allow-secret
);
let alice_decision = admin_policy.evaluate(&mut alice_exchange).await;
let bob_decision = admin_policy.evaluate(&mut bob_exchange).await;
match alice_decision {
Ok(AuthorizationDecision::Granted { principal }) => {
println!(
"Alice vs RolePolicy[admin]: GRANTED (subject={})",
principal.subject
);
}
Ok(AuthorizationDecision::Denied { reason, .. }) => {
println!("Alice vs RolePolicy[admin]: DENIED ({reason})");
}
Err(e) => println!("Alice vs RolePolicy[admin]: ERROR ({e})"),
_ => println!("Alice vs RolePolicy[admin]: UNKNOWN decision"),
}
match bob_decision {
Ok(AuthorizationDecision::Granted { principal }) => {
println!(
"Bob vs RolePolicy[admin]: GRANTED (subject={})",
principal.subject
);
}
Ok(AuthorizationDecision::Denied { reason, .. }) => {
println!("Bob vs RolePolicy[admin]: DENIED ({reason})");
}
Err(e) => println!("Bob vs RolePolicy[admin]: ERROR ({e})"),
_ => println!("Bob vs RolePolicy[admin]: UNKNOWN decision"),
}
println!();
println!("--- Route with Security Policy ---");
let mut ctx = CamelContext::builder().build().await.unwrap(); // allow-unwrap
ctx.register_component(TimerComponent::new());
ctx.register_component(LogComponent::new());
let role_policy = RolePolicy::new(vec!["admin".to_string()], true, false, validator.clone());
let wrapped = BearerInjectingPolicy::new(alice_token.access_token.to_string(), role_policy);
let secured_route = RouteBuilder::from("timer:tick?period=2000&repeatCount=2")
.route_id("admin-only-route")
.security_policy(SecurityPolicyConfig::new(wrapped))
.to("log:info?showHeaders=true")
.build()?;
ctx.add_route_definition(secured_route).await?;
YAML equivalent for the secured route
The Rust example wraps RolePolicy in BearerInjectingPolicy to inject a
static demo token. In a YAML route, the bearer token arrives in the transport
Authorization header and the policy is declarative.
routes:
- id: admin-only-route
from: timer:tick?period=2000&repeatCount=2
security_policy:
roles: [admin]
all_required: true
steps:
- to: log:info?showHeaders=true
YAML security_policy accepts one of roles, scopes, ref, wasm, or
permission. Routes with security_policy do not support the canonical
hot-reload path.
WASM authorization policy
A WASM plugin can serve as an authorization policy. The plugin reads camel.auth.* properties from the Exchange and returns a grant or denial.
let fixtures_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures");
let wasm_path = fixtures_dir.join("role-check.wasm");
let registry = Arc::new(std::sync::Mutex::new(camel_core::Registry::new()));
// This example uses the programmatic WasmSecurityPolicy::new() API.
// For production routes, prefer Camel.toml registration via
// [security.policies.wasm.<name>] + YAML `security_policy: wasm: <name>`.
// See crates/components/camel-component-wasm/README.md for details.
let wasm_policy = WasmSecurityPolicy::new(
&wasm_path,
WasmConfig::default(),
Arc::new(camel_core::RegistryComponentContext::new(registry)),
HashMap::new(),
)
.await?;
let policy =
AuthenticatedWasmPolicy::new(validator, alice_token.access_token.to_string(), wasm_policy);
Note: Service registration is Rust API only. YAML routes compile to the same
RouteDefinition. The service wiring stays in application code.
YAML equivalent for a production route
Register the WASM policy in Camel.toml under
[security.policies.wasm.<name>], then reference it by name in the route.
routes:
- id: wasm-secured-route
from: timer:tick?period=1000&repeatCount=5
security_policy:
wasm: role-check
steps:
- to: log:info?showHeaders=true
For production routes, prefer Camel.toml registration through [security.policies.wasm.<name>] with YAML security_policy: wasm: <name>.
See ADR-0050 for the WASM sandbox capability posture.
Security defaults
The startup-validation phase enforces fail-closed security defaults. Routes refuse to start when required configuration is missing. See ADR-0033 for the full policy.
Reference: camel-auth crate