Authentication and authorization for IDAC-PL

We would like to test (and migrate) to central authorization/authentication for polish IDAC. Our current setup uses keycloak which uses github (specific organization) as IDP. As far as I understand we would need client secret and configuration created at USDAC. Details from our side are the following (please let us know if we missed anything):

Following suggestion from Knuth - tagging @rra and @frossie . Would you find some time to help here :slight_smile: ?

These OIDC clients have been set up but are not yet activated yet, since at present we only add new OIDC clients during our weekly maintenance window. I will set them up next Thursday during our patch window and then will message you a URL where you can get the client IDs and secrets to use on your end.

This has been set up and I sent you the credentials via DM.

Hi, thanks for creds, copying went OK. I’ll should be able to test this before end of the week, I’ll let you know

@rra - could you change the redirects to

https://rsp.cis.gov.pl/keycloak/realms/rsp/broker/data-lsst-cloud/endpoint

(for the production site) and

https://rsp2.cis.gov.pl/keycloak/realms/rsp/broker/data-lsst-cloud/endpoint # devel site

(for the development site)?

Apparently my mind went on autopilot while writing the original request :smiley:

This should now be updated. Let us know if you have any problems.

Thanks ! This helped, things got further this time :). I have one minor issue - in the token the actuall issuer is

“iss”:“https://data.lsst.cloud/

while in https://data.lsst.cloud/.well-known/openid-configuration the “issuer” field lacks tha trailing “/”. This results in the following error in keycloak:

2026-03-23 10:16:07,183 ERROR [org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (executor-thread-1) Failed to make identity provider oauth callback: org.keycloak.broker.provider.IdentityBrokerException: Wrong issuer from token. Got: https://data.lsst.cloud/ expected: https://data.lsst.cloud

Unfortunatelly it seems once the external idp is configured in keycloak (at least via .well-known endpoint) it is not possible to change the issuer field (keycloak UI gives the possibility, but it seems it is not having any effect). For now correct way is to configure the idp in keycloak by hand (ie without using well-known for automatic configuration), with correct issuer from start. In longer run - would you be able to make entry in .well-known/openid-configuration consistent with the one in token?

For another thing I’m not exactly sure if it is another keycloak problem or bug on our side (understood as rsp stack - most likely in gafaelfawr - and not this specific deployment) . After workarounding the issuer problem I get the following error visible in keycloak logs:

2026-03-23 10:27:21,735 ERROR [org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (executor-thread-6) Failed to make identity provider oauth callback: org.keycloak.broker.provider.IdentityBrokerException: Could not fetch attributes from userinfo endpoint.

(…)
Caused by: java.lang.IllegalArgumentException: Last unit does not have enough valid bits
at java.base/java.util.Base64$Decoder.decode0(Base64.java:872)
(…)

I saw this earlier while debugging the first issue - at least one part of the “id_token” does not contain base64 padding. Please compare the results of:

echo eyJhbGciOiJSUzI1NiIsImtpZCI6ImdhZmFlbGZhd3IiLCJ0eXAiOiJKV1QifQ | base64 -d # the actual part of the token
→ {“alg”:“RS256”,“kid”:“gafaelfawr”,“typ”:“JWT”}base64: invalid input

and

echo eyJhbGciOiJSUzI1NiIsImtpZCI6ImdhZmFlbGZhd3IiLCJ0eXAiOiJKV1QifQ== | base64 -d # padding added manually
→ (proper decode, without base64 warning message)

Do you happen to know if openid standard requires padding to be present? This determines if I should open a keycloak issue or this should be fixed on rsp stack side

Hi Tomasz,

The first problem (the mismatch of issuer because of the trailing slash) is definitely a bug. I’ve found the problem and it should be fixed in our Thursday maintenance window this week.

For the second, could you double-check that the error is coming from decoding the id token? JWT requires use of base64url encoding, which prohibits adding padding, so it would be weird if a major implementation such as Keycloak were complaining about that. See:

https://www.rfc-editor.org/rfc/rfc7515.html#section-2

under Base64url Encoding. I suspect it’s complaining about some other part of the userinfo response, but I’m not sure what it might be complaining about. Although, that said, I’m not sure what else it might be trying to decode, since the other values returned from the userinfo endpoint are not base64-encoded.

Hi Russ,

I managed to further debug the problem. Prior to the error, a call is made to /auth/openid/token. In response we get

    {"access_token":"AA-BBBBBBBB-CCCCCCCCCCCCC.ZZZZZZZZZZZZZZZZZZZZZZ","id_token": "(....)","expires_in":1191489,"scope":"openid profile","token_type":"Bearer"}

keycloak then splits the access_token using “.” as delimeter and tries to base64Url.decode the first part (i.e. “AA-BBBBBBBB-CCCCCCCCCCCCC”), which leads to the error message from previous post.

I’m having trouble interpreting this (as my openid experience is limited). In your opinion - is the base64 decoding of the first part of access_token something it should try to perform or this operation makes completly no sense?

As final info - If I try decoding the part before the dot in python (using base64.urlsafe_b64decode() method) I get the following error message:

    binascii.Error: Invalid base64-encoded string: number of data characters (25) cannot be 1 more than a multiple of 4

It does need to split it apart in that way and decode the first part. The part before the period is the JOSE header, which it needs to decode to understand how to decode the rest.

It appears to be using the wrong base64 decoding algorithm because it’s expecting padding, which is prohibited in JWTs. I’m baffled by why it is handling this incorrectly, but you can verify that the token decodes correctly by pasting it into https://www.jwt.io/ (this will leak your username and full name to that web site, if you care).

Python weirdly does not provide an out-of-the-box function for decoding base64url encoding (unfortunately, this is not the same thing as urlsafe_b64decode). The supported approach seems to be to manually add the padding. For example, from the PyJWT source:

def base64url_decode(input: Union[bytes, str]) -> bytes:
    input_bytes = force_bytes(input)

    rem = len(input_bytes) % 4

    if rem > 0:
        input_bytes += b"=" * (4 - rem)

    return base64.urlsafe_b64decode(input_bytes)

Oh, or also in Python you could use base64url · PyPI

Hmmm… Should the access_id field from /auth/openid/token response be a JWT token? I’m getting here data with only two fields (i.e. a single dot), as far as I (naively) understand there should be three of them in case of JWT.

BTW do you happen to know if any of IDACs has a working keycloak setup with central auth service? If the access_id is ok, it is likely some misconfiguration on my side.

For completeness - I tried running the above code snippet on the first field of access_id (i.e. part before the dot, data variable in code) :

import base64
from typing import Union

data = b'....'

def force_bytes(value: Union[bytes, str]) -> bytes:
    if isinstance(value, str):
        return value.encode("utf-8")
    elif isinstance(value, bytes):
        return value
    else:
        raise TypeError("Expected a string value")

def base64url_decode(input: Union[bytes, str]) -> bytes:
    input_bytes = force_bytes(input)

    rem = len(input_bytes) % 4

    if rem > 0:
        input_bytes += b"=" * (4 - rem)

    return base64.urlsafe_b64decode(input_bytes)


print(base64url_decode(data))

but this leads to the same error I posted above:

Traceback (most recent call last):
  File "/home/tfruboes/work_2026_1/2026.03.buildKeycloak/keycloak/dec.py", line 32, in <module>
    print(base64url_decode(data))
          ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tfruboes/work_2026_1/2026.03.buildKeycloak/keycloak/dec.py", line 28, in base64url_decode
    return base64.urlsafe_b64decode(input_bytes)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/tfruboes/.pyenv/versions/3.12.11/lib/python3.12/base64.py", line 134, in urlsafe_b64decode
    return b64decode(s)
           ^^^^^^^^^^^^
  File "/home/tfruboes/.pyenv/versions/3.12.11/lib/python3.12/base64.py", line 88, in b64decode
    return binascii.a2b_base64(s, strict_mode=validate)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
binascii.Error: Invalid base64-encoded string: number of data characters (25) cannot be 1 more than a multiple of 4

When it comes to jwt.io - it also complains about not having three fields.

OK, so I further debugged keycloak. It has a promising setting of “Access Token is JWT” (isAccessTokenJWT when dumping realm config to json) which should control how acsess token is treated. Unfortunately this one is completely useless as there is an override hardcoded:

if I run keycloak with this line commented things get to proceed (to the moment where it crashes when writing to ldap due to missing mappers config).

So - the question is if the access_token (from /auth/openid/token path response in gafaelfawr) should always be a JWT token (I would guess the answer is no, but once again - I lack experience here :slight_smile: ).

For now on I’ll continue working with patched keycloak image (i.e. accepting access_token as it is currently).

— Edit
After some digging - it seems, that access token is not required to be a JWT (Final: OpenID Connect Core 1.0 incorporating errata set 2). The override shown above looks like a development artifact, as it was included in the same commit as said configuration options (Fix issue with access tokens claims not being imported using OIDC IDP… · keycloak/keycloak@a8bca52 · GitHub)

Ah, that makes more sense. Yes, indeed, access_id in our environment is an opaque token, not a JWT. It is not base64-encoded. Hopefully that Keycloak bug can be fixed.

I made a PR allready (Disable configuration override forcing access token to be of JWT type by fruboes · Pull Request #47826 · keycloak/keycloak · GitHub ), fix seems to be trivial. Lets see if they merge this sometime soon

BTW are there plans/possibilities to extend set of parameters returned by lsst auth endpoint (i.e. as documented in Configuring OpenID Connect — Gafaelfawr )? At this point a numerical identifier may be convenient for some IDACs, so uidNumber/gidNumber ldap attributes could be easily populated on user first IDAC login.

Notes:

  • having a running ldap instance is enforced by gafaelfawr’ logic when setup involves keycloak (or similar)
  • automatic uidNumber generation AFAIU is not supported by openldap (other servers likely the same)
  • there is also a matter of uid range - we dont want the numbers to be of too low value in order not to mix with typical system uids (in my case there are also other range considerations but I would rather talk about them during our meetings and not write them down here)
  • Finally - I think GitHub - barche/keycloak-ldap-posixaccount · GitHub could be a basis for a solution if numeric uid cannot be added to our openid response for some reason. But this may mean we would need to maintain our own keycloak image builds (as keycloak version is set during the plugin build; I dont know how problematic is version mismatch)

Regarding the uid/gid issue, the approach that I am going with for now for the UKIDAC is to create new users with a uid of -1 and run a minimal service that simply looks for those users and assigns a real uid and gid. The code is here: https://github.com/lsst-uk/aai-proxy/blob/main/uidset/uidset.py As you note, a keycloak plugin could also do the job, but with all the downsides that you mention.

Clearly for systems where the ldap is only used for the RSP and isn’t trying to integrate with anything else, getting the uid and gid from the IDP as you suggest would be the cleanest solution.