akhq icon indicating copy to clipboard operation
akhq copied to clipboard

OIDC with Azure | akhq doesn't map roles coming from azure ad to groups | no debug logs

Open youcefguichi opened this issue 1 year ago • 4 comments

We are a bit on pressure to deliver this and i would really appreciate some help.

When mapping individual users all is good. However when using the groups as in the commented sections the mapping doesn't really work and i kept being redirected to the login page. What makes it harder to debug is that there is no logging i tried to set the level to debug but it still only showing warn and info, so i'm not sure which part is causing the problem and how to debug it.

      oidc:
        enabled: true
        providers:
          azure:
            label: "Click here to Login with Azure"
            username-field: email
            groups-field: roles
            users:
            - username: [email protected]
              groups:
                - admin
            # default-group: topic-admin
            # groups:
            #   - name: reader
            #     groups:
            #       - admin

youcefguichi avatar Mar 27 '25 13:03 youcefguichi

Do you have a sample of the claim provided by Azure ? The only reason that it works for user mapping and not for group mapping should be the groups-fields that is not referencing the right field from the claim

AlexisSouquiere avatar Mar 27 '25 16:03 AlexisSouquiere

hey @AlexisSouquiere , Thanks a bunch for getting back to me, i try to give a detailed explanation. Long story short: based on my mimictions it looks like the first senario doesn't include any roles/groups in the claim, but the second scenario does include roles/claims

Senario 1 (akhq undestand this flow, but no roles/groups included in the claim)

When i mimic the config below, which leads to construct the following url, the grenerated jwt token doesn't include roles or groups, it only include email with other info, i attached the genrated jwt sample below.

Example auth url

https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize?client_id=<client_id>&response_type=code&redirect_uri=http://localhost:9091/oauth/callback/azure&scope=openid profile email

example config

oauth2:
    enabled: true
    debug: true
    clients:
        azure:
        client-id: "<client-id>"
        client-secret: "<client_secret>"
        scopes:
            - openid
            - email
            - profile
        openid:
            issuer: "https://login.microsoftonline.com/<tenant-id>/v2.0"

generated jwt structure from the config

{
    "typ": "JWT",
    "nonce": "<nonce>",
    "alg": "RS256",
    "x5t": "<x5t>",
    "kid": "<kid>"
}.{
    "aud": "<audience>",
    "iss": "https://sts.windows.net/<tenant-id>/",
    "iat": <issued-at>,
    "nbf": <not-before>,
    "exp": <expiration>,
    "acct": <account>,
    "acr": "<acr>",
    "acrs": [
        "<acr-values>"
    ],
    "aio": "<aio>",
    "altsecid": "<altsecid>",
    "amr": [
        "<authentication-methods>"
    ],
    "app_displayname": "<app-display-name>",
    "appid": "<app-id>",
    "appidacr": "<app-id-acr>",
    "email": "<email>",
    "idp": "https://sts.windows.net/<idp-tenant-id>/",
    "idtyp": "<id-type>",
    "ipaddr": "<ip-address>",
    "name": "<name>",
    "oid": "<object-id>",
    "platf": "<platform>",
    "puid": "<puid>",
    "rh": "<rh>",
    "scp": "<scopes>",
    "sid": "<session-id>",
    "sub": "<subject>",
    "tenant_region_scope": "<tenant-region-scope>",
    "tid": "<tenant-id>",
    "unique_name": "<unique-name>",
    "uti": "<uti>",
    "ver": "<version>",
    "wids": [
        "<wids>"
    ],
    "xms_ftd": "<xms-ftd>",
    "xms_idrel": "<xms-idrel>",
    "xms_st": {
        "sub": "<xms-st-sub>"
    },
    "xms_tcdt": <xms-tcdt>,
    "xms_tdbr": "<xms-tdbr>"
}.[Signature]

Senario 2 (akhq doesn't understand this flow, roles/groups are included in the claim)

When i mimic the second senario and expose my entra app under this URI api://client-id/.default and then request authorization with uri scope, i do receive roles and groups in the claim

Example auth url

https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize?client_id=<client_id>&response_type=code&redirect_uri=http://localhost:9091/oauth/callback/azure&scope=client-id/.default

Example config used

    oauth2:
        enabled: true
        debug: true
        clients:
          azure:
            client-id: "<client-id>"
            client-secret: "<client-secret>"
            scopes:
              - client-id/.default
            openid:
              issuer: "https://login.microsoftonline.com/2efewfwef/v2.0"

Example Generated jwt structre, (akhq did not worked with the above config)

{
    "typ": "JWT",
    "alg": "RS256",
    "kid": "<key-id>"
}.{
    "aud": "<audience>",
    "iss": "https://login.microsoftonline.com/<tenant-id>/v2.0",
    "iat": <issued-at>,
    "nbf": <not-before>,
    "exp": <expiration>,
    "aio": "<aio>",
    "azp": "<authorized-party>",
    "azpacr": "<authorized-party-acr>",
    "groups": [
        "<group-id-1>",
        "<group-id-2>"
    ],
    "idp": "https://sts.windows.net/<idp-tenant-id>/",
    "name": "<name>",
    "oid": "<object-id>",
    "preferred_username": "<preferred-username>",
    "rh": "<rh>",
    "roles": [
        "<role-1>"
    ],
    "scp": "<scopes>",
    "sid": "<session-id>",
    "sub": "<subject>",
    "tid": "<tenant-id>",
    "uti": "<uti>",
    "ver": "<version>",
    "wids": [
        "<wids-1>",
        "<wids-2>",
        "<wids-3>"
    ]
}.[Signature]

However with this example config, akhq doesn't understand this flow and i receive the following error:

message	'Internal Server Error: Cannot invoke "String.contains(java.lang.CharSequence)" because "token" is null'
_links	
self	
href	"/oauth/callback/azure?code=gowrhgowrhgowhwvgworhgiwuhgfuwrgh379t6735yg3ty78n0%3d&session_state=c34ct34t35ty3"
templated	false
_embedded	
stacktrace	
0	
message	'java.lang.NullPointerException: Cannot invoke "String.contains(java.lang.CharSequence)" because "token" is null\n\tat io.micronaut.security.token.jwt.validator.JwtValidator.hasAtLeastTwoDots(JwtValidator.java:101)\n\tat io.micronaut.security.token.jwt.validator.JwtValidator.validate(JwtValidator.java:79)\n\tat io.micronaut.security.oauth2.endpoint.token.response.validation.DefaultOpenIdTokenResponseValidator.parseJwtWithValidSignature(DefaultOpenIdTokenResponseValidator.java:162)\n\tat io.micronaut.security.oauth2.endpoint.token.response.validation.DefaultOpenIdTokenResponseValidator.validate(DefaultOpenIdTokenResponseValidator.java:90)\n\tat io.micronaut.security.oauth2.endpoint.authorization.response.DefaultOpenIdAuthorizationResponseHandler.validateOpenIdTokenResponse(DefaultOpenIdAuthorizationResponseHandler.java:238)\n\tat io.micronaut.security.oauth2.endpoint.authorization.response.DefaultOpenIdAuthorizationResponseHandler.createAuthenticationResponse(DefaultOpenIdAuthorizationResponseHandler.java:198)\n\tat io.micronaut.security.oauth2.endpoint.authorization.response.DefaultOpenIdAuthorizationResponseHandler.lambda$handle$0(DefaultOpenIdAuthorizationResponseHandler.java:133)\n\tat reactor.core.publisher.FluxSwitchMapNoPrefetch$SwitchMapMain.subscribeInner(FluxSwitchMapNoPrefetch.java:210)\n\tat reactor.core.publisher.FluxSwitchMapNoPrefetch$SwitchMapMain.onNext(FluxSwitchMapNoPrefetch.java:164)\n\tat reactor.core.publisher.FluxPublishOn$PublishOnSubscriber.runAsync(FluxPublishOn.java:446)\n\tat reactor.core.publisher.FluxPublishOn$PublishOnSubscriber.run(FluxPublishOn.java:533)\n\tat io.micronaut.core.propagation.PropagatedContext.lambda$wrap$3(PropagatedContext.java:211)\n\tat reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)\n\tat reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)\n\tat io.micronaut.core.propagation.PropagatedContext.lambda$wrap$4(PropagatedContext.java:228)\n\tat io.micrometer.core.instrument.composite.CompositeTimer.recordCallable(CompositeTimer.java:129)\n\tat io.micrometer.core.instrument.Timer.lambda$wrap$1(Timer.java:203)\n\tat io.micronaut.core.propagation.PropagatedContext.lambda$wrap$4(PropagatedContext.java:228)\n\tat io.micrometer.core.instrument.composite.CompositeTimer.recordCallable(CompositeTimer.java:129)\n\tat io.micrometer.core.instrument.Timer.lambda$wrap$1(Timer.java:203)\n\tat java.base/java.util.concurrent.FutureTask.run(Unknown Source)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)\n\tat java.base/java.lang.Thread.run(Unknown Source)\n

youcefguichi avatar Mar 27 '25 21:03 youcefguichi

hey @AlexisSouquiere, any update concerning this one? Thank you!

youcefguichi avatar Mar 31 '25 14:03 youcefguichi

I just tried and I'm able to make it works on my side. Your scenario 1 looks good except that you need to add the groups claim in the tokens.

My config looks like this (the micronaut.security.oauth2.client is the same as yours):

akhq:
  security:
    oidc:
      providers:
        azure:
          label: "Login with Azure"
          username-field: preferred_username
          groups-field: groups
          groups:
            - name: <my_group_guid>
              groups:
                - admin

In Azure, I added the groups claim (doc here): Image

When login I can see in the id_token that it contains a list of all my groups (guid in the test I did) and it maps it with the one I put in my AKHQ configuration

AlexisSouquiere avatar Apr 02 '25 09:04 AlexisSouquiere

why the use-oidc-claim: true is not used for your configuration

atanu-debnath avatar Jun 25 '25 16:06 atanu-debnath

I just tried and I'm able to make it works on my side. Your scenario 1 looks good except that you need to add the groups claim in the tokens.

My config looks like this (the micronaut.security.oauth2.client is the same as yours):

akhq: security: oidc: providers: azure: label: "Login with Azure" username-field: preferred_username groups-field: groups groups: - name: <my_group_guid> groups: - admin

In Azure, I added the groups claim (doc here): Image

When login I can see in the id_token that it contains a list of all my groups (guid in the test I did) and it maps it with the one I put in my AKHQ configuration

hey @AlexisSouquiere, thanks for your support, i was missing the GUID of the group, it works perfectly thanks for your support.

youcefguichi avatar Jul 02 '25 11:07 youcefguichi