Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. TokenAuthenticator validates a bearer or API token and returns a Principal. Implementations include IntrospectionAuthenticator (RFC 7662), ApiKeyAuthenticator, StaticTokenAuthenticator, and LocalJwtValidator.
  2. ClaimsMapper maps token or introspection claims into Principal fields: subject, roles, scopes, issuer, audience. JsonPointerClaimsMapper resolves JSON Pointer paths, so any OIDC provider works without code.
  3. PermissionEvaluator evaluates resource, action, and scope requests and returns a PermissionDecision. Route-level security_policy.permission calls 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