Repository: provectus/kafka-ui
Issue: RBAC with GitHub Enterprise Server
Labels: type/bug
status/pending
scope/backend
status/accepted

Hello,

I am trying to use Kafka UI with OAuth and RBAC in an isolated environment (no Internet access).

I configured OAuth for GitHub Enterprise Server 3.6.8 like this:
```yaml
auth:
  type: OAUTH2
  oauth2:
    client:
      github:
        provider: github
        clientId: xxx
        clientSecret: xxx
        scope:
          - read:org
        user-name-attribute: login
        custom-params:
          type: github
        authorization-uri: https://xxx/login/oauth/authorize
        token-uri: https://xxx/login/oauth/access_token
        user-info-uri: https://xxx/api/v3/user
```
It works fine: I can login to Kafka UI by using GitHub Enterprise.

However, I cannot use RBAC, because Kafka UI is trying to connect to `api.github.com`. It looks like some parts are hardcoded in GithubAuthorityExtractor.java:
https://github.com/provectus/kafka-ui/blob/5c723d9b4491019bc6de01e3746b5b86d70f0560/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GithubAuthorityExtractor.java#L28
Also, like this one: `.uri("/user/orgs")`

It would be great to use `UserInfoUri` for RBAC with GitHub provider.

Issue comments:
Hello there sti-yvi! 👋

Thank you and congratulations 🎉 for opening your very first issue in this project! 💖

In case you want to claim this issue, please comment down below! We will try to get back to you as soon as we can. 👀
Hi, thanks for reaching out.

What's `UserInfoUri` exactly? Not really familiar with on-premises github, is that possible to retrieve this information just with a different URI?
Hello, thank you for the quick answer!

> What's `UserInfoUri` exactly? Not really familiar with on-premises github, is that possible to retrieve this information just with a different URI?

Well, I found this field here:
https://github.com/provectus/kafka-ui/blob/c5d6896ae1f61dea2ace3fdda6e78817eaca6c4b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthProperties.java#L38

It is also mentioned in #3249.

It looks like this is an URI to retrieve user information. This URI can look like this:
- github.com: `https://api.github.com/user` (doc [here](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#3-use-the-access-token-to-access-the-api))
- GitHub Enterprise: `http(s)://HOSTNAME/api/v3/user` (doc [here](https://docs.github.com/en/enterprise-server@3.6/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#3-use-the-access-token-to-access-the-api))
>Also, like this one: `.uri("/user/orgs")`

this one is consistent for both APIs.

I've implemented the usage of `UserInfoUri`, please try this docker image `public.ecr.aws/provectus/kafka-ui-custom-build:3545` and let me know. Don't forget to pass a custom `user-info-uri`, btw.
> > Also, like this one: `.uri("/user/orgs")`
> 
> this one is consistent for both APIs.

Understood. I put `user-info-uri: https://xxx/api/v3` in the configuration file.

With `public.ecr.aws/provectus/kafka-ui-custom-build:3545` image, it goes to `/login?error`. Below, logs with `TRACE` level:
```
2023-03-24 09:32:31,152 TRACE [reactor-http-epoll-3] o.s.h.c.j.Jackson2JsonDecoder: [492b2eea] [e326097b-1, L:/xxx.xxx.xxx.xxx:54634 - R:xxx/yyy.yyy.yyy.yyy:443] Decoded [{access_token=zzz, token_type=bearer, scope=read:org}]
2023-03-24 09:32:31,194 TRACE [reactor-http-epoll-3] o.s.w.r.f.c.ExchangeFunctions: [597c6943] HTTP GET https://xxx/api/v3, headers={masked}
2023-03-24 09:32:31,202 DEBUG [reactor-http-epoll-3] r.n.r.PooledConnectionProvider: [00c69207] Created a new pooled channel, now: 1 active connections, 0 inactive connections and 0 pending acquire requests.
2023-03-24 09:32:31,203 DEBUG [reactor-http-epoll-3] r.n.t.SslProvider: [00c69207] SSL enabled using engine SSLEngine[hostname=xxx, port=443, Session(1679650351202|SSL_NULL_WITH_NULL_NULL)] and SNI xxx/<unresolved>:443
2023-03-24 09:32:31,203 DEBUG [reactor-http-epoll-3] r.n.t.TransportConfig: [00c69207] Initialized pipeline DefaultChannelPipeline{(reactor.left.sslHandler = io.netty.handler.ssl.SslHandler), (reactor.left.sslReader = reactor.netty.tcp.SslProvider$SslReadHandler), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.left.httpDecompressor = io.netty.handler.codec.http.HttpContentDecompressor), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
2023-03-24 09:32:31,203 DEBUG [reactor-http-epoll-3] r.n.r.DefaultPooledConnectionProvider: [e326097b, L:/xxx.xxx.xxx.xxx:54634 - R:xxx/yyy.yyy.yyy.yyy:443] onStateChange(POST{uri=/login/oauth/access_token, connection=PooledConnection{channel=[id: 0xe326097b, L:/xxx.xxx.xxx.xxx:54634 - R:xxx/yyy.yyy.yyy.yyy:443]}}, [response_completed])
2023-03-24 09:32:31,203 DEBUG [reactor-http-epoll-3] r.n.r.DefaultPooledConnectionProvider: [e326097b, L:/xxx.xxx.xxx.xxx:54634 - R:xxx/yyy.yyy.yyy.yyy:443] onStateChange(POST{uri=/login/oauth/access_token, connection=PooledConnection{channel=[id: 0xe326097b, L:/xxx.xxx.xxx.xxx:54634 - R:xxx/yyy.yyy.yyy.yyy:443]}}, [disconnecting])
2023-03-24 09:32:31,203 DEBUG [reactor-http-epoll-3] r.n.r.DefaultPooledConnectionProvider: [e326097b, L:/xxx.xxx.xxx.xxx:54634 - R:xxx/yyy.yyy.yyy.yyy:443] Releasing channel
2023-03-24 09:32:31,217 DEBUG [reactor-http-epoll-3] r.n.r.PooledConnectionProvider: [e326097b, L:/xxx.xxx.xxx.xxx:54634 - R:xxx/yyy.yyy.yyy.yyy:443] Channel cleaned, now: 0 active connections, 1 inactive connections and 0 pending acquire requests.
2023-03-24 09:32:31,217 DEBUG [reactor-http-epoll-3] r.n.t.TransportConnector: [00c69207] Connecting to [xxx/yyy.yyy.yyy.yyy:443].
2023-03-24 09:32:31,219 DEBUG [reactor-http-epoll-3] r.n.r.DefaultPooledConnectionProvider: [00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] Registering pool release on close event for channel
2023-03-24 09:32:31,219 DEBUG [reactor-http-epoll-3] r.n.r.PooledConnectionProvider: [00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] Channel connected, now: 1 active connections, 1 inactive connections and 0 pending acquire requests.
2023-03-24 09:32:31,244 DEBUG [reactor-http-epoll-3] j.e.security:  TLSHandshake: xxx:443, TLSv1.3, TLS_AES_128_GCM_SHA256, -831680270
2023-03-24 09:32:31,245 DEBUG [reactor-http-epoll-3] i.n.h.s.SslHandler: [id: 0x00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] HANDSHAKEN: protocol:TLSv1.3 cipher suite:TLS_AES_128_GCM_SHA256
2023-03-24 09:32:31,245 DEBUG [reactor-http-epoll-3] r.n.r.DefaultPooledConnectionProvider: [00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] onStateChange(PooledConnection{channel=[id: 0x00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443]}, [connected])
2023-03-24 09:32:31,245 DEBUG [reactor-http-epoll-3] r.n.r.DefaultPooledConnectionProvider: [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] onStateChange(GET{uri=null, connection=PooledConnection{channel=[id: 0x00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443]}}, [configured])
2023-03-24 09:32:31,245 DEBUG [reactor-http-epoll-3] r.n.h.c.HttpClientConnect: [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] Handler is being applied: {uri=https://xxx/api/v3, method=GET}
2023-03-24 09:32:31,245 DEBUG [reactor-http-epoll-3] r.n.r.DefaultPooledConnectionProvider: [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] onStateChange(GET{uri=/api/v3, connection=PooledConnection{channel=[id: 0x00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443]}}, [request_prepared])
2023-03-24 09:32:31,254 DEBUG [reactor-http-epoll-3] r.n.r.DefaultPooledConnectionProvider: [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] onStateChange(GET{uri=/api/v3, connection=PooledConnection{channel=[id: 0x00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443]}}, [request_sent])
2023-03-24 09:32:31,286 DEBUG [reactor-http-epoll-3] r.n.h.c.HttpClientOperations: [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] Received response (auto-read:false) : RESPONSE(decodeResult: success, version: HTTP/1.1)
HTTP/1.1 200 OK
Server: <filtered>
Date: <filtered>
Content-Type: <filtered>
Transfer-Encoding: <filtered>
Cache-Control: <filtered>
Vary: <filtered>
ETag: <filtered>
X-OAuth-Scopes: <filtered>
X-Accepted-OAuth-Scopes: <filtered>
x-oauth-client-id: <filtered>
X-GitHub-Enterprise-Version: <filtered>
X-GitHub-Media-Type: <filtered>
X-RateLimit-Limit: <filtered>
X-RateLimit-Remaining: <filtered>
X-RateLimit-Reset: <filtered>
X-RateLimit-Used: <filtered>
x-ratelimit-resource: <filtered>
Access-Control-Expose-Headers: <filtered>
Access-Control-Allow-Origin: <filtered>
X-GitHub-Request-Id: <filtered>
Strict-Transport-Security: <filtered>
X-Frame-Options: <filtered>
X-Content-Type-Options: <filtered>
X-XSS-Protection: <filtered>
Referrer-Policy: <filtered>
Content-Security-Policy: <filtered>
X-Runtime-Rack: <filtered>
2023-03-24 09:32:31,286 DEBUG [reactor-http-epoll-3] r.n.r.DefaultPooledConnectionProvider: [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] onStateChange(GET{uri=/api/v3, connection=PooledConnection{channel=[id: 0x00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443]}}, [response_received])
2023-03-24 09:32:31,287 TRACE [reactor-http-epoll-3] o.s.w.r.f.c.ExchangeFunctions: [597c6943] [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] Response 200 OK, headers={masked}
2023-03-24 09:32:31,288 DEBUG [reactor-http-epoll-3] r.n.c.FluxReceive: [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] FluxReceive{pending=0, cancelled=false, inboundDone=false, inboundError=null}: subscribing inbound receiver
2023-03-24 09:32:31,297 DEBUG [reactor-http-epoll-3] r.n.h.c.HttpClientOperations: [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] Received last HTTP packet
2023-03-24 09:32:31,297 TRACE [reactor-http-epoll-3] r.n.c.ChannelOperations: [00c69207, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] Disposing ChannelOperation from a channel
java.lang.Exception: ChannelOperation terminal stack
        at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:465)
        at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:702)
        at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:113)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:336)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:308)
        at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1373)
        at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1236)
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1285)
        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:519)
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:458)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:280)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:800)
        at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe$1.run(AbstractEpollChannel.java:425)
        at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)
        at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
        at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
        at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:403)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:833)
2023-03-24 09:32:31,299 TRACE [reactor-http-epoll-3] o.s.h.c.j.Jackson2JsonDecoder: [597c6943] [00c69207-1, L:/xxx.xxx.xxx.xxx:54636 - R:xxx/yyy.yyy.yyy.yyy:443] Decoded [{current_user_url=https://xxx/api/v3/user, current_user_authorizations_html_url=https://xxx/settings/connections/applications{/client_id}, authorizations_url=https://xxx/api/v3/authorizations, code_search_url=https://xxx/api/v3/search/code?q={query}{&page,per_page,sort,order}, commit_search_url=https://xxx/api/v3/search/commits?q={query}{&page,per_page,sort,order}, emails_url=https://xxx/api/v3/user/emails, emojis_url=https://xxx/api/v3/emojis, events_url=https://xxx/api/v3/events, feeds_url=https://xxx/api/v3/feeds, followers_url=https://xxx/api/v3/user/followers, following_url=https://xxx/api/v3/user/following{/target}, gists_url=https://xxx/api/v3/gists{/gist_id}, hub_url=https://xxx/api/v3/hub, issue_search_url=https://xxx/api/v3/search/issues?q={query}{&page,per_page,sort,order}, issues_url=https://xxx/api/v3/issues, keys_url=https://xxx/api/v3/user/keys, label_search_url=https://xxx/api/v3/search/labels?q={query}&repository_id={repository_id}{&page,per_page}, notifications_url=https://xxx/api/v3/notifications, organization_url=https://xxx/api/v3/orgs/{org}, organization_repositories_url=https://xxx/api/v3/orgs/{org}/repos{?type,page,per_page,sort}, organization_teams_url=https://xxx/api/v3/orgs/{org}/teams, public_gists_url=https://xxx/api/v3/gists/public, rate_limit_url=https://xxx/api/v3/rate_limit, repository_url=https://xxx/api/v3/repos/{owner}/{repo}, repository_search_url=https://xxx/api/v3/search/repositories?q={query}{&page,per_page,sort,order}, current_user_repositories_url=https://xxx/api/v3/user/repos{?type,page,per_page,sort}, starred_url=https://xxx/api/v3/user/starred{/owner}{/repo}, starred_gists_url=https://xxx/api/v3/gists/starred, topic_search_url=https://xxx/api/v3/search/topics?q={query}{&page,per_page}, user_url=https://xxx/api/v3/users/{user}, user_organizations_url=https://xxx/api/v3/user/orgs, user_repositories_url=https://xxx/api/v3/users/{user}/repos{?type,page,per_page,sort}, user_search_url=https://xxx/api/v3/search/users?q={query}{&page,per_page,sort,order}}]
2023-03-24 09:32:31,302 DEBUG [reactor-http-epoll-3] o.s.w.r.f.c.ExchangeFunctions: [597c6943] Cancel signal (to close connection)
2023-03-24 09:32:31,309 DEBUG [reactor-http-epoll-3] o.s.s.w.s.a.AuthenticationWebFilter: Authentication failed: [invalid_user_info_response] An error occurred reading the UserInfo response: Missing attribute 'login' in attributes
2023-03-24 09:32:31,309 DEBUG [reactor-http-epoll-3] o.s.s.w.s.DefaultServerRedirectStrategy: Redirecting to '/login?error'
2023-03-24 09:32:31,309 TRACE [reactor-http-epoll-3] o.s.w.s.a.HttpWebHandlerAdapter: [ff0aa489-3] Completed 302 FOUND, headers={masked}
```


It looks like `UserInfoUri` is something else, sorry.
@Haarolean, maybe you should put the API endpoint property somewhere else (like under `customParams`)?
>Understood. I put user-info-uri: https://xxx/api/v3 in the configuration file.

Almost, please try setting it to `http(s)://HOSTNAME/api/v3/user` instead.
> Almost, please try setting it to `http(s)://HOSTNAME/api/v3/user` instead.

With `http(s)://HOSTNAME/api/v3/user`, it does request to `http(s)://HOSTNAME/api/v3/user/user/orgs`, so it is now working:
*redacted as unrelated*
@sti-yvi wait, is it working? Now the URL seems invalid, looks like the previous one was correct.
I didn't take a look at your logs, it states this: `An error occurred reading the UserInfo response: Missing attribute 'login' in attributes`. No idea what is this about considering I've tried that build with an non-premises github and it works fine.
> @sti-yvi wait, is it working?

No, it does not work with both URI.

> Now the URL seems invalid, looks like the previous one was correct.

Indeed.

> I didn't take a look at your logs, it states this: `An error occurred reading the UserInfo response: Missing attribute 'login' in attributes`. No idea what is this about considering I've tried that build with an non-premises github and it works fine.

I think my first assumption (*It would be great to use `UserInfoUri` for RBAC with GitHub provider.*) was wrong, sorry. It looks like `UserInfoUri` is used by something else, so `UserInfoUri` must not be used here.

Can you use a `customParams` instead of `UserInfoUri` in `GithubAuthorityExtractor.java`, please? This  `customParams` should allow to set an alternative GitHub API endpoint.

>I think my first assumption (It would be great to use UserInfoUri for RBAC with GitHub provider.) was wrong, sorry. It looks like UserInfoUri is used by something else, so UserInfoUri must not be used here.

Nope, I'm confident that's the right assumption, see [here](https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java#L65).

It still puzzles me why you do receive `Missing attribute 'login' in attributes`. 
I've applied a little fix, which reduces the duplication in the URL (`/user/user`), so both standard & cloud URLs should work. Let's see if that does fix the issue.

Now, let's set your URL again to `http(s)://HOSTNAME/api/v3/user` and **repull the image** and let's try the same thing again.

> Nope, I'm confident that's the right assumption, see [here](https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java#L65).

Nice catch.

> Now, let's set your URL again to `http(s)://HOSTNAME/api/v3/user` and **repull the image** and let's try the same thing again.

Yes, it works now! 🎉 
I tested a read-only and admin setups on two Kafka clusters, permissions work as expected.

Thank you for your time and your work. 👏 
@Haarolean may I suggest to update the [*OAuth-Configuration* wiki page](https://github.com/provectus/kafka-ui/wiki/OAuth-Configuration) with [following content](https://github.com/provectus/kafka-ui/files/11091031/OAuth-Configuration.md)?



@sti-yvi our new docs are located at docs.kafka-ui.provectus.io. Can you raise a PR of [this file](https://github.com/provectus/kafka-ui/blob/docs/configuration/authentication/oauth2.md) into `docs` branch? :)
> @sti-yvi our new docs are located at docs.kafka-ui.provectus.io. Can you raise a PR of [this file](https://github.com/provectus/kafka-ui/blob/docs/configuration/authentication/oauth2.md) into `docs` branch? :)

Sure. PR #3577 opened.
Tested with v0.7.0: all is good. Thank you again for your work @Haarolean.

Pull request review comments:
get it from OAuth2UserRequest

Patch that resolved the issue:
diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GithubAuthorityExtractor.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GithubAuthorityExtractor.java
index 882c727375a..0f66e45917e 100644
--- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GithubAuthorityExtractor.java
+++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GithubAuthorityExtractor.java
@@ -12,6 +12,7 @@
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.http.HttpHeaders;
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
 import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
 import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
 import org.springframework.web.reactive.function.client.WebClient;
@@ -24,8 +25,7 @@ public class GithubAuthorityExtractor implements ProviderAuthorityExtractor {
   private static final String USERNAME_ATTRIBUTE_NAME = "login";
   private static final String ORGANIZATION_NAME = "login";
   private static final String GITHUB_ACCEPT_HEADER = "application/vnd.github+json";
-
-  private final WebClient webClient = WebClient.create("https://api.github.com");
+  private static final String DUMMY = "dummy";
 
   @Override
   public boolean isApplicable(String provider) {
@@ -64,9 +64,24 @@ public Mono<Set<String>> extract(AccessControlService acs, Object value, Map<Str
       return Mono.just(groupsByUsername);
     }
 
+    OAuth2UserRequest req = (OAuth2UserRequest) additionalParams.get("request");
+    String infoEndpoint = req.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri();
+
+    if (infoEndpoint == null) {
+      infoEndpoint = CommonOAuth2Provider.GITHUB
+          .getBuilder(DUMMY)
+          .clientId(DUMMY)
+          .build()
+          .getProviderDetails()
+          .getUserInfoEndpoint()
+          .getUri();
+    }
+
+    WebClient webClient = WebClient.create(infoEndpoint);
+
     final Mono<List<Map<String, Object>>> userOrganizations = webClient
         .get()
-        .uri("/user/orgs")
+        .uri("/orgs")
         .headers(headers -> {
           headers.set(HttpHeaders.ACCEPT, GITHUB_ACCEPT_HEADER);
           OAuth2UserRequest request = (OAuth2UserRequest) additionalParams.get("request");
diff --git a/kafka-ui-api/src/main/resources/application-local.yml b/kafka-ui-api/src/main/resources/application-local.yml
index 5eb3772e2bb..5e8fa18f407 100644
--- a/kafka-ui-api/src/main/resources/application-local.yml
+++ b/kafka-ui-api/src/main/resources/application-local.yml
@@ -6,6 +6,9 @@ logging:
     #org.springframework.http.codec.json.Jackson2JsonDecoder: DEBUG
     reactor.netty.http.server.AccessLog: INFO
 
+#server:
+#  port: 8080 #- Port in which kafka-ui will run.
+
 kafka:
   clusters:
     - name: local
@@ -42,27 +45,40 @@ kafka:
 spring:
   jmx:
     enabled: true
-  security:
-    oauth2:
-      client:
-        registration:
-          cognito:
-            clientId: xx
-            clientSecret: yy
-            scope: openid
-            client-name: cognito
-            provider: cognito
-            redirect-uri: http://localhost:8080/login/oauth2/code/cognito
-            authorization-grant-type: authorization_code
-        provider:
-          cognito:
-            issuer-uri: https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_M7cIUn1nj
-            jwk-set-uri: https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_M7cIUn1nj/.well-known/jwks.json
-            user-name-attribute: username
+
 auth:
   type: DISABLED
-
-roles.file: /tmp/roles.yml
-
-#server:
-#  port: 8080 #- Port in which kafka-ui will run.
+#  type: OAUTH2
+#  oauth2:
+#    client:
+#      cognito:
+#        clientId:
+#        clientSecret:
+#        scope: openid
+#        client-name: cognito
+#        provider: cognito
+#        redirect-uri: http://localhost:8080/login/oauth2/code/cognito
+#        authorization-grant-type: authorization_code
+#        issuer-uri: https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_M7cIUn1nj
+#        jwk-set-uri: https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_M7cIUn1nj/.well-known/jwks.json
+#        user-name-attribute: username
+#        custom-params:
+#          type: cognito
+#          logoutUrl: https://kafka-ui.auth.eu-central-1.amazoncognito.com/logout
+#      google:
+#        provider: google
+#        clientId:
+#        clientSecret:
+#        user-name-attribute: email
+#        custom-params:
+#          type: google
+#          allowedDomain: provectus.com
+#      github:
+#        provider: github
+#        clientId:
+#        clientSecret:
+#        scope:
+#          - read:org
+#        user-name-attribute: login
+#        custom-params:
+#          type: github


Test patch: