Skip to content

CAMEL-24319: camel-keycloak add optional token type (typ) and authorized party (azp) validation - #25259

Merged
oscerd merged 2 commits into
apache:mainfrom
oscerd:fix/CAMEL-24319
Jul 31, 2026
Merged

CAMEL-24319: camel-keycloak add optional token type (typ) and authorized party (azp) validation#25259
oscerd merged 2 commits into
apache:mainfrom
oscerd:fix/CAMEL-24319

Conversation

@oscerd

@oscerd oscerd commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What

Adds two opt-in hardening settings to KeycloakSecurityPolicy, so a Camel-protected route can validate the token's type (typ) and authorized party (azp) in addition to the existing signature / issuer / active / (opt-in) audience checks:

  • expectedTokenTypes — comma-separated allow-list of accepted typ values (e.g. "Bearer"). Guards against token-type confusion, e.g. an ID token or refresh token being presented where an access token is expected.
  • expectedAuthorizedParty — expected azp value (e.g. "my-client"); ensures the token was issued for the expected client.

Both checks are applied on both validation paths (local JWT verification and token introspection) and are disabled by default for backward compatibility, mirroring the existing expectedAudience opt-in (CAMEL-23875).

Why

KeycloakSecurityPolicy already verifies signature, issuer, active state and optionally audience, but never checked typ/azp. keycloak-core itself ships a TokenTypeCheck, and OAuth2 resource servers are expected to reject non-access-token types. This closes that gap without changing any default behaviour.

Tests

Four new unit tests in KeycloakSecurityHelperTest (accept/reject for both typ and azp); full camel-keycloak suite green (27 tests, 0 failures).

Docs

keycloak-security.adoc gains a "Token Type and Authorized Party Validation" section. No upgrade-guide entry — the change is opt-in and non-breaking.

Jira: https://issues.apache.org/jira/browse/CAMEL-24319


Authored by Claude Code on behalf of Andrea Cosentino.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

…zed party (azp) validation

Adds two opt-in KeycloakSecurityPolicy settings, disabled by default for
backward compatibility and applied on both the local JWT verification and
token introspection paths:

- expectedTokenTypes: allow-list of accepted "typ" values, guarding against
  token-type confusion (e.g. an ID or refresh token presented where an
  access token is expected)
- expectedAuthorizedParty: expected "azp" value, ensuring the token was
  issued for the expected client

Mirrors the existing expectedAudience opt-in (CAMEL-23875). Includes unit
tests and documentation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • catalog/camel-catalog
  • components/camel-keycloak

🔬 Scalpel shadow comparison — Scalpel: 10 tested, 28 compile-only — current: 9 all tested

Maveniverse Scalpel detected 38 affected modules (current approach: 9).

⚠️ Modules only in Scalpel (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

Skip-tests mode would test 10 modules (2 direct + 8 downstream), skip tests for 28 (generated code, meta-modules)

Modules Scalpel would test (10)
  • camel-catalog
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-keycloak
  • camel-launcher-container
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
Modules with tests skipped (28)
  • apache-camel
  • camel-allcomponents
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (38 modules)
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated)
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: Kamelet Main
  • Camel :: Keycloak
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

Thanks for adding token type and authorized party validation to camel-keycloak! The feature is well-structured and the JWT validation path looks solid. I found a few issues that should be addressed before merging:


🔴 HIGH — validatePermissions() missing token type and authorized party checks on introspection path

The PR adds validateTokenTypeFromIntrospection() and validateAuthorizedPartyFromIntrospection() calls to both authenticateToken() and validateRoles(), but not to validatePermissions().

In beforeProcess(), when only requiredPermissions is configured (no roles), the flow skips authenticateToken() entirely (the condition is !rolesRequired && !permissionsRequired) and goes straight to validatePermissions(). On its introspection branch, validatePermissions() checks active status, issuer, and audience — but does not call the two new validation methods.

This means token type and authorized party validation can be bypassed when useTokenIntrospection=true with only permissions configured.

The prior CAMEL-23875 commit (4bad2c9) correctly added audience validation to all three code paths (authenticateToken, validateRoles, validatePermissions). This PR should follow the same pattern.

Suggested fix — add the missing calls to validatePermissions() in the introspection branch, analogous to how they were added in validateRoles():

// In validatePermissions(), introspection branch, after audience validation:
validateTokenTypeFromIntrospection(introspectionResult, exchange);
validateAuthorizedPartyFromIntrospection(introspectionResult, exchange);

🟡 MEDIUM — validateTokenTypeFromIntrospection() reads getClaim("typ") instead of standard token_type field

The new validateTokenTypeFromIntrospection() method uses introspectionResult.getClaim("typ") to read the token type. However, RFC 7662 defines the standard introspection response field as "token_type", and IntrospectionResult already has a typed accessor getTokenType() that reads claims.get("token_type").

While Keycloak does forward the JWT typ claim in its introspection response, using the non-standard field name makes the code fragile against non-Keycloak introspection endpoints. Consider using getTokenType() or checking both fields for robustness.


🟡 LOW — No tests for introspection validation paths

The PR adds tests to KeycloakSecurityHelperTest for the local JWT verification path, but the new validateTokenTypeFromIntrospection() and validateAuthorizedPartyFromIntrospection() methods have no test coverage.

The CAMEL-23875 commit included 5 introspection-specific tests in KeycloakSecurityProcessorTest (e.g., testTokenMissingExpectedAudienceRejectedWithRequiredPermissionsIntrospection), establishing a precedent that introspection paths should be tested. Adding similar tests for the new validation methods would ensure complete coverage.

…issions-only introspection path

gnodet's review found that validateTokenTypeFromIntrospection/
validateAuthorizedPartyFromIntrospection were wired into authenticateToken()
and validateRoles() but not validatePermissions(). With useTokenIntrospection=true
and only requiredPermissions configured, beforeProcess() goes straight to
validatePermissions(), so both checks could be bypassed. Add them to that path
with the same gating (matching the CAMEL-23875 audience pattern), and add two
introspection tests that reject a permission-satisfying token with the wrong
token type / azp. Also document why the "typ" claim (JWT token category that
Keycloak forwards) is used rather than the RFC 7662 "token_type" OAuth field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@oscerd

oscerd commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @gnodet — all three addressed in 67c2962:

🔴 HIGH — validatePermissions() bypass: Confirmed and fixed. Added validateTokenTypeFromIntrospection and validateAuthorizedPartyFromIntrospection to the validatePermissions() introspection branch (after the audience check), with the same gating (!getExpectedTokenTypesAsList().isEmpty() / !ObjectHelper.isEmpty(getExpectedAuthorizedParty())), matching the CAMEL-23875 audience pattern. Good catch — with a permissions-only + introspection config the flow does skip authenticateToken/validateRoles, so both checks were bypassable.

🟡 LOW — tests: Added two introspection tests mirroring testTokenMissingExpectedAudienceRejectedWithRequiredPermissionsIntrospection: testTokenWrongTokenTypeRejectedWithRequiredPermissionsIntrospection (token has the permission but typ=Refresh while Bearer is expected) and testTokenWrongAuthorizedPartyRejectedWithRequiredPermissionsIntrospection (permission ok but azp=attacker-client). Both assert the route is not reached. 11 tests pass.

🟡 MEDIUM — typ vs token_type: I kept getClaim("typ") deliberately and added a code comment explaining why. This feature validates the JWT typ claim — the token category (Keycloak emits Bearer/Refresh/ID) — which Keycloak forwards on its introspection response. RFC 7662 token_type is the OAuth token type (Bearer) and can't distinguish an access token from a refresh/ID token, so it can't express what this check is for. Since this is camel-keycloak, relying on Keycloak's introspection contract is reasonable; using getTokenType() would validate the wrong field. Happy to reconsider if you'd prefer an explicit token_type fallback, but it would change the semantics.

Re-requesting review.

Claude Code on behalf of Andrea Cosentino (@oscerd).

@oscerd
oscerd requested a review from gnodet July 31, 2026 08:08

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review after latest commits. All three previous findings are properly addressed:

  1. Security bypass in validatePermissions() (HIGH) — Fixed. validateTokenTypeFromIntrospection() and validateAuthorizedPartyFromIntrospection() are now called in the introspection branch of validatePermissions() (lines 512-520), matching the pattern established by CAMEL-23875 for audience validation across all three code paths. ✅

  2. typ vs token_type rationale (MEDIUM) — Addressed with a clear code comment (lines 448-450). The JWT typ claim (Bearer/Refresh/ID) distinguishes token categories, while RFC 7662 token_type is always Bearer — sound reasoning for a Keycloak-specific component. ✅

  3. Missing introspection tests (LOW) — Fixed with two new tests: testTokenWrongTokenTypeRejectedWithRequiredPermissionsIntrospection and testTokenWrongAuthorizedPartyRejectedWithRequiredPermissionsIntrospection specifically covering the bypass scenario. ✅

Fix commit is minimal and focused: 13 lines in the processor, 79 lines of tests. CI passes.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of Guillaume Nodet

@oscerd oscerd added the enhancement New feature or request label Jul 31, 2026
@oscerd oscerd self-assigned this Jul 31, 2026
@oscerd oscerd added this to the 4.22.0 milestone Jul 31, 2026
@oscerd
oscerd merged commit bb49225 into apache:main Jul 31, 2026
6 checks passed
@oscerd
oscerd deleted the fix/CAMEL-24319 branch July 31, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants