{"id":12286,"date":"2026-05-20T14:12:00","date_gmt":"2026-05-20T19:12:00","guid":{"rendered":"https:\/\/www.rushworth.us\/lisa\/?p=12286"},"modified":"2026-05-20T15:15:04","modified_gmt":"2026-05-20T20:15:04","slug":"kafbat-oauth-with-rbac","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=12286","title":{"rendered":"Kafbat &#8211; OAUTH With RBAC"},"content":{"rendered":"\n<p>I encountered a challenge with a Kafka management tool &#8212; it supports SSO, and I was able to get an OAUTH connection set up to control what <em>users<\/em> could see when logging in through the UI, but the API component didn&#8217;t extract information from the bearer token and there was nothing in the rbac mapping to allow the bearer-token client ID to access anything. <\/p>\n\n\n\n<p>Updates to allow the \/api components to be authenticated by simple bearer tokens and a client ID mapped into a role are at <a href=\"https:\/\/github.com\/ljr55555\/kafka-ui\">https:\/\/github.com\/ljr55555\/kafka-ui<\/a><\/p>\n\n\n\n<p><strong>AuthorizationController.java <\/strong>was updated to properly support non-browser, machine-principal auth.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Added\/fixed:<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>avoids null failure when <code>authentication.getName()<\/code> is missing<\/li>\n\n\n\n<li>resolves principal name from alternate attributes such as:\n<ul class=\"wp-block-list\">\n<li><code>client_id<\/code><\/li>\n\n\n\n<li><code>sub<\/code><\/li>\n\n\n\n<li><code>username<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>updated displayed permissions logic so <code>\/api\/authorization<\/code> uses the same effective RBAC matching idea as the backend<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Result<\/h4>\n\n\n\n<p><code>\/api\/authorization<\/code> now works for bearer-token API callers and shows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>username = client ID<\/li>\n\n\n\n<li>populated permissions list<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><code>AccessControlService.java<\/code><\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Added support for API bearer-token principals<\/h4>\n\n\n\n<p>Previously, <code>getUser()<\/code> only worked when the authenticated principal was a <code>RbacUser<\/code>, which covered the browser\/user flow.<\/p>\n\n\n\n<p>Now it can also derive an <code>AuthenticatedUser<\/code> from opaque-token authenticated principals by extracting:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>principal name<\/li>\n\n\n\n<li>group-like values from attributes\/authorities if present<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Updated role matching logic<\/h4>\n\n\n\n<p>Previously, role matching was only role name matches one of <code>user.groups()<\/code>. Now it also supports role name matches <code>user.principal()<\/code>. That enables RBAC binding directly to the API client ID.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Result<\/h4>\n\n\n\n<p>RBAC now works for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>normal browser users via groups<\/li>\n\n\n\n<li>API bearer-token callers via client principal name<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><code>DynamicConfigMapper.java<\/code><\/h3>\n\n\n\n<p>Fixed a mapper bug.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Before<\/h4>\n\n\n\n<p>The method mapping resource server config created a populated <code>OAuth2ResourceServerProperties result<\/code> object but always returned <code>null<\/code>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">After<\/h4>\n\n\n\n<p>It now returns <code>result<\/code>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Result<\/h4>\n\n\n\n<p>Dynamic\/config mapping for resource-server settings no longer silently discards the mapped object.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Build\/package note<\/h2>\n\n\n\n<p>To preserve the browser UI, the jar needs to be built with frontend included &#8212; which you know if you <a href=\"https:\/\/ui.docs.kafbat.io\/development\/building\/with-docker\">read the doc<\/a> &#8230; or you take my route, start it all up, test the API successfully, and then get baffled that the user UI throws <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\n        bash.\/gradlew clean assemble -Pinclude-frontend=true\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">application.yml<\/h2>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\nserver:\n  port: 8443\n  ssl:\n    enabled: true\n    key-store: file:\/etc\/kafkaui\/certs\/kafbat.rushworth.us.p12\n    key-store-password: ${KEYSTORE_PASSWORD}\n    key-store-type: PKCS12\n    key-alias: kafbat\n\nauth:\n  type: OAUTH2\n  oauth2:\n    client:\n      pingfed:\n        client-id: ${OAUTH_CLIENT_ID}\n        client-secret: ${OAUTH_CLIENT_SECRET}\n        scope:\n          - openid\n          - profile\n          - email\n        client-name: oauthclient\n        provider: oauthclient\n        redirect-uri: https:\/\/kafbat.rushworth.us:8443\/login\/oauth2\/code\/oauthclient\n        authorization-grant-type: authorization_code\n        issuer-uri: https:\/\/login.example.com\n        jwk-set-uri: https:\/\/login.example.com\/pf\/JWKS\n        authorization-uri: https:\/\/login.example.com\/as\/authorization.oauth2\n        token-uri: https:\/\/login.example.com\/as\/token.oauth2\n        user-info-uri: https:\/\/login.example.com\/idp\/userinfo.openid\n        user-name-attribute: username\n        custom-params:\n          type: oauth\n          roles-field: memberOf\n\n    resource-server:\n      opaque-token:\n        client-id: ${OAUTH_CLIENT_ID}\n        client-secret: ${OAUTH_CLIENT_SECRET}\n        introspection-uri: https:\/\/login.example.com\/as\/introspect.oauth2\n\nkafka:\n  clusters:\n    - name: test\n      bootstrapServers: ${KAFKA_BOOTSTRAP_SERVERS} \n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">roles.yml<\/h2>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\nrbac:\n  roles:\n    - name: &quot;admins&quot;\n      clusters:\n        - test\n      subjects:\n        - provider: oauth\n          type: role\n          value: &quot;CN=KafbatAdmins,OU=SecurityGroups,DC=example,DC=com&quot;\n      permissions:\n        - resource: applicationconfig\n          actions: all\n\n        - resource: clusterconfig\n          actions: all\n\n        - resource: topic\n          value: &quot;.*&quot;\n          actions: all\n\n        - resource: consumer\n          value: &quot;.*&quot;\n          actions: all\n\n        - resource: schema\n          value: &quot;.*&quot;\n          actions: all\n\n        - resource: connect\n          value: &quot;.*&quot;\n          actions: all\n\n        - resource: ksql\n          actions: all\n\n        - resource: acl\n          actions: &#x5B; view ]\n\n    - name: &quot;${OAUTH_CLIENT_ID}&quot;\n      clusters:\n        - test\n      subjects:\n        - provider: oauth\n          type: user\n          value: &quot;${OAUTH_CLIENT_ID}&quot;\n      permissions:\n        - resource: applicationconfig\n          actions: all\n\n        - resource: clusterconfig\n          actions: all\n\n        - resource: topic\n          value: &quot;.*&quot;\n          actions: all\n\n        - resource: consumer\n          value: &quot;.*&quot;\n          actions: all\n\n        - resource: schema\n          value: &quot;.*&quot;\n \n        - resource: connect\n          value: &quot;.*&quot;\n          actions: all\n\n        - resource: ksql\n          actions: all\n\n        - resource: acl\n          actions: &#x5B; view ]\n \n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">docker-compose.yml<\/h2>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\nservices:\n  redpanda:\n    image: redpandadata\/redpanda:v25.1.2\n    container_name: redpanda\n    command:\n      - redpanda\n      - start\n      - --overprovisioned\n      - --smp=1\n      - --memory=1G\n      - --reserve-memory=0M\n      - --node-id=0\n      - --check=false\n      - --kafka-addr=PLAINTEXT:\/\/0.0.0.0:9092\n      - --advertise-kafka-addr=PLAINTEXT:\/\/redpanda:9092\n    ports:\n      - &quot;9092:9092&quot;\n\n  kafbat-ui:\n    image: ghcr.io\/kafbat\/kafka-ui:latest\n    container_name: kafbat-ui\n    restart: unless-stopped\n    depends_on:\n      - redpanda\n    ports:\n      - &quot;8443:8443&quot;\n    volumes:\n      - .\/config\/application.yml:\/etc\/kafkaui\/application.yml:ro\n      - .\/config\/roles.yml:\/etc\/kafkaui\/roles.yml:ro\n      - .\/certs:\/etc\/kafkaui\/certs:ro\n    environment:\n      SPRING_CONFIG_LOCATION: file:\/etc\/kafkaui\/application.yml\n      SPRING_CONFIG_ADDITIONAL_LOCATION: file:\/etc\/kafkaui\/roles.yml\n      KEYSTORE_PASSWORD: REDACTED\n      OAUTH_CLIENT_ID: REDACTED\n      OAUTH_CLIENT_SECRET: REDACTED\n      KAFKA_BOOTSTRAP_SERVERS: redpanda:9092\n\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>I encountered a challenge with a Kafka management tool &#8212; it supports SSO, and I was able to get an OAUTH connection set up to control what users could see when logging in through the UI, but the API component didn&#8217;t extract information from the bearer token and there was nothing in the rbac mapping &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1547],"tags":[2200,1488],"class_list":["post-12286","post","type-post","status-publish","format-standard","hentry","category-java","tag-kafbat","tag-oauth"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12286","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=12286"}],"version-history":[{"count":2,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12286\/revisions"}],"predecessor-version":[{"id":12291,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12286\/revisions\/12291"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=12286"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=12286"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=12286"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}