keycloak / keycloak

Open Source Identity and Access Management For Modern Applications and Services
https://www.keycloak.org
Apache License 2.0
22.85k stars 6.69k forks source link

Access token and ID token have broken format #27814

Closed urpylka closed 3 months ago

urpylka commented 7 months ago

Before reporting an issue

Area

oidc

Describe the bug

Hello, there is 2 problems with json of access token / id token when it setup in same state in 2 mappers in client scope.

I have Client scope - roles, it has 3 mappers. 2 of it:

  1. client roles
  2. realm roles

When I setup mappers to Add to ID token in enable / disable state in both mappers. It raise that the id token doesn't have } in the end of json.

Same situation with Add to access token: when I set it in enable / disable state in both mappers. It raise that the access token doesn't have } in the end of json.

That's work only if state of these options different in 2 mappers.

Version

tested on 21.1.1 and 23.0.7

Regression

Expected behavior

Everytime the jsons must be valid.

Actual behavior

When in 2 mappers I set same state to:

I got that in the end of json in id/access token there is not ended symbol }

How to Reproduce?

You should to create an client with:

Use random urls, generate client_id + client_secret and use it in next request:

RESULT=$(curl -k --noproxy '*' -d 'client_id=<OBFUSCATED>' -d 'username=<OBFUSCATED>' -d 'password=<OBFUSCATED>' -d 'grant_type=password' -d 'client_secret=<OBFUSCATED>' -d 'scope=openid' 'https://keycloak.example.com/auth/realms/<OBFUSCATED>/protocol/openid-connect/token')

# show access token
echo $RESULT | sed 's/.*access_token":"\([^"]*\).*/\1/' | awk -F. '{print $2}' | base64 -d | jq .

# show id token
echo $RESULT | sed 's/.*id_token":"\([^"]*\).*/\1/' | awk -F. '{print $2}' | base64 -d | jq .

Anything else?

Also an another one small problem:

If Service accounts roles in Capability config is off, I am still getting (when configured client role mapper):

  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
urpylka commented 7 months ago

I am new for Keycloak, and accept that I may do smth wrong. If it is please point me to correct way

urpylka commented 7 months ago

Checked on 24.0.1 - the same situation

urpylka commented 7 months ago

Update: tested on another realm, situation a little bit other. Parameters work only if in client role mapper are set in On.

Looks like there is problem with boolean algebra

urpylka commented 7 months ago

On ubuntu

>$ base64 --version
base64 (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Simon Josefsson.

it is automatically fix json but return stderr mes base64: invalid input

on mac

>$ base64 --version
FreeBSD base64

it return json without end symbol }

keycloak-github-bot[bot] commented 7 months ago

Due to the amount of issues reported by the community we are not able to prioritise resolving this issue at the moment.

If you are affected by this issue, upvote it by adding a :thumbsup: to the description. We would also welcome a contribution to fix the issue.

ceriath commented 6 months ago

I happened to stumble upon this while having a similar issue and digged a bit into it. It seems like the issue here is the padding. Adding the required works results in a working decode. Here is a snippet that works with coreutils base64:

decode_padded_jwt(){
        echo -n $1 | cut -d '.' -f "1" | base64 -d | jq -r
        echo -n $1 | cut -d '.' -f "2" | base64 -d | jq -r
}

decode_unpadded_jwt(){
        echo -n $1 | cut -d '.' -f "1" | sed -s 's/$/====/' | fold -w4 | sed -s '$ d' | tr -d '\n' | base64 -d | jq -r
        echo -n $1 | cut -d '.' -f "2" | sed -s 's/$/====/' | fold -w4 | sed -s '$ d' | tr -d '\n' | base64 -d | jq -r
}

A correctly padded base64 string should work with the first command, however the jwts issues by keycloak don't:

>:~$ decode_padded_jwt $(cat my_jwt_token)
base64: invalid input
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "Kq2...pEdi4"
}
base64: invalid input
{
  "exp": 1713374181,
  "iat": 1713373881,
[...]

Using the same file and adding the padding manually, it works as intended:

>:~$ decode_unpadded_jwt $(cat my_jwt_token)
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "Kq2...pEdi4"
}
{
  "exp": 1713371861,
  "iat": 1713370061,
[...]

Short explanation of the snippet: as base64 requires to be divisible by 4, it simply adds 4 padding characters, cuts everything into 4 character blocks and removes the last block. The blocks are then put back together, yielding a padded base64 encoded string.

Since sometimes the string just happens to be divisible by 4, it still works sometimes.

RFC 7515 actually says with all trailing '=' characters omitted, so the implementation by keycloak is correct, unfortunately this causes some standard base64 tools to fail while trying to decode jwts. So the solution is to use a decoder, that supports unpadded base64 according to RFC 4648 3.2 (or add padding characters yourself). In my case, go has base64.RawStdEncoding instead of base64.StdEncoding for such use-cases.


tl;dr This is not a bug, but works as designed. The solution is to use a decoder that supports unpadded base64.

keycloak-github-bot[bot] commented 3 months ago

Due to lack of updates in the last 180 days this issue will be automatically closed.