Skip to content

feat: JWT role extractor for RBAC without interactive OAuth#1840

Draft
lrudolph333 wants to merge 4 commits into
kafbat:mainfrom
lrudolph333:feat/jwt-rbac-resource-server
Draft

feat: JWT role extractor for RBAC without interactive OAuth#1840
lrudolph333 wants to merge 4 commits into
kafbat:mainfrom
lrudolph333:feat/jwt-rbac-resource-server

Conversation

@lrudolph333

@lrudolph333 lrudolph333 commented May 7, 2026

Copy link
Copy Markdown

Summary

Resolves #1828

When kafbat is configured with oauth2ResourceServer to validate bearer JWTs, the RBAC authority extractors do not run — JWT-authenticated requests bypass role-based access control entirely because Spring Security's default JwtAuthenticationToken principal is a Jwt object, not an RbacUser.

This PR adds a configurable ReactiveJwtAuthenticationConverter that:

  1. Extracts username from a configurable JWT claim (fallback to sub)
  2. Extracts roles from a configurable JWT claim (supports List, Set, or comma-separated String)
  3. Matches them against configured RBAC role subjects (provider: oauth, type: user/role)
  4. Produces an RbacJwtAuthenticationToken whose principal implements RbacUser, making existing RBAC enforcement work seamlessly for bearer token requests

Configuration

auth:
  type: OAUTH2
  oauth2:
    resource-server:
      jwt:
        jwk-set-uri: https://my-idp.example.com/.well-known/jwks.json
    resource-server-rbac:
      roles-claim: "ter"       # JWT claim containing roles
      username-claim: "tei"    # JWT claim for principal name

RBAC role definitions work unchanged:

rbac:
  roles:
    - name: "streaming"
      clusters: [prod]
      subjects:
        - provider: oauth
          type: role
          value: "streaming"
      permissions:
        - resource: topic
          value: ".*"
          actions: [VIEW, MESSAGES_READ]

Changes

  • OAuthProperties.java — Added ResourceServerRbac config class with rolesClaim/usernameClaim
  • RbacJwtUser.java — New record implementing RbacUser for JWT principals
  • RbacJwtAuthenticationToken.java — Custom AbstractAuthenticationToken with RbacJwtUser principal
  • RbacReactiveJwtAuthenticationConverter.java — Converter wiring JWT claims → RBAC role matching
  • OAuthSecurityConfig.java — Wires converter into resource server JWT path when config is present

Backward Compatibility

  • When resource-server-rbac is not configured, behavior is completely unchanged (no converter installed)
  • Existing oauth2Login flow and all provider-specific extractors are unaffected
  • Existing RBAC role definitions with provider: oauth subjects work for both login and resource server paths

Test plan

  • Unit tests: RbacReactiveJwtAuthenticationConverterTest — 10 cases covering role lists, comma-separated strings, username matching, sub fallback, empty groups, regex subjects
  • Integration tests: JwtResourceServerRbacTest — 6 cases using WireMock JWKS + RSA-signed JWTs verifying end-to-end decode → role extraction → principal creation
  • All existing OAuth/RBAC tests pass (no regressions)
  • Checkstyle passes
  • Deployed internally with the following environment, non-admin folks can no longer see message viewing

🤖 Generated with Claude Code

When kafbat is configured with oauth2ResourceServer to validate bearer
JWTs, the RBAC authority extractors were not invoked, causing JWT-
authenticated requests to bypass role-based access control entirely.

This adds a configurable ReactiveJwtAuthenticationConverter that extracts
roles and username from JWT claims, matches them against configured RBAC
role subjects (provider=OAUTH), and produces an authentication token
whose principal implements RbacUser — making the existing RBAC
enforcement work seamlessly for bearer token requests.

Configuration:
  auth.oauth2.resource-server-rbac.roles-claim: "ter"
  auth.oauth2.resource-server-rbac.username-claim: "tei"

Closes: kafbat#1828

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
@kapybro kapybro Bot added status/triage Issues pending maintainers triage area/rbac Related to Role Based Access Control feature status/triage/manual Manual triage in progress status/triage/completed Automatic triage completed labels May 7, 2026
@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5fa8eb44-0986-4996-ae9b-acae91708326

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kapybro kapybro Bot removed the status/triage Issues pending maintainers triage label May 7, 2026
… default role

Support IdPs like Slack Toolbelt where roles live in custom claims (e.g.
`ter`) rather than standard `sub`. Adds:
- `entity-type-claim` config for coarse-grained access by token type
  (e.g. all `nebula` service tokens get a `service` RBAC role)
- `default-role` fallback when no subject mapping matches
- New subject type `entity-type` for RBAC role definitions

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Trey Rudolph and others added 2 commits May 28, 2026 16:13
…ations

When auth.type=OAUTH2 is set but no OAuth2 client providers are configured,
the app previously threw "OAuth2 authentication is enabled but no providers
specified." This change makes interactive login beans (client registration
repository, token response client, user services, logout handler) conditional
on having client registrations, allowing kafbat to run purely as an OAuth2
resource server validating bearer JWTs.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…server-only mode

Spring Security's ReactiveOAuth2ClientConfiguration requires a
ReactiveClientRegistrationRepository bean to exist even when no OAuth2
client login flow is configured. Returning null caused NoSuchBeanDefinitionException.
Instead, provide a no-op lambda implementation that returns Mono.empty() for
any registration lookup.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/rbac Related to Role Based Access Control feature status/triage/completed Automatic triage completed status/triage/manual Manual triage in progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JWT role extractor for RBAC without interactive OAuth

1 participant