Layer7 API Management

Decoding a JSON Web Token (JWT) issued by Keycloak (RedHat SSO) 

Oct 04, 2017 09:03 PM

Hello everyone. I've been working with something cool and decided to share some of my findings with you.

 

This particular prospect is using the Keycloak (or RedHat SSO) as their token server and also to federate identities (SAML). They are evaluating our API gateway in front of their API calls to validate the existing JWT and also to enforce other security rules. I'm going to share with you the tweaks and steps I did to validate (decode) the signature of the issued JWT.

 

The Keycloak token server is slightly different than ours. When you call its REST endpoint, asking for an access_token, it will return a huge hash as both the access_token and refresh_tokens. Those are actually JWT with a bunch of information about the user and the client, inside of it. This is an example of one of those big tokens:

 

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJaNkF0NnhoU2lCOTQtdEItcHZfT1VHeUdFbXVRbzFhWWwwcXczSFFTZE5zIn0.eyJqdGkiOiJiMjgyMTU3NC1hMWEzLTQ2M2QtODFmMC02OWU4OGY0MTUwMjgiLCJleHAiOjE1MDcxNjQzMDUsIm5iZiI6MCwiaWF0IjoxNTA3MTY0MDA1LCJpc3MiOiJodHRwczovL2tleWNsb2Frc3NvLmdwbS5zb2x1dGlvbnM6ODQ0My9hdXRoL3JlYWxtcy9xdWlja3N0YXJ0IiwiYXVkIjoiYXBwLXByb2ZpbGUtdmFuaWxsYSIsInN1YiI6IjhhYjY3YzcyLWRkNTUtNGU3Mi05NmUxLWE0NDYwZGQ3OTRkZiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcC1wcm9maWxlLXZhbmlsbGEiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiIxMzU0Y2ZjMy03ZDVjLTQ5OTgtOGRiYi1lZGM4YzcyOWZkMTgiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHBzOi8va2V5Y2xvYWtzc28uZ3BtLnNvbHV0aW9uczoyODU0MyJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJyZWFsbS1tYW5hZ2VtZW50Ijp7InJvbGVzIjpbInZpZXctcmVhbG0iLCJ2aWV3LWlkZW50aXR5LXByb3ZpZGVycyIsIm1hbmFnZS1pZGVudGl0eS1wcm92aWRlcnMiLCJpbXBlcnNvbmF0aW9uIiwicmVhbG0tYWRtaW4iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS1hdXRob3JpemF0aW9uIiwibWFuYWdlLWNsaWVudHMiLCJxdWVyeS1ncm91cHMiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sIm5hbWUiOiJSdWJlbnMgU291emEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJydWJlbnMiLCJnaXZlbl9uYW1lIjoiUnViZW5zIiwiZmFtaWx5X25hbWUiOiJTb3V6YSIsImVtYWlsIjoicnViZW5zLmRlc291emFAY2EuY29tIn0.gE3LzF6U-h_VVesIfVCQbxHqcNJXTMLl6t-es-gu5l2U1zV3C9JTgH7nNGemQyR3MPo2aBykThn1IjaYklF0lxxCyZZbxFhcm91AYXwnqqp5mKJouJBaQyYoAHSxb-ltdndL7Pi0tjK5vMyDlFi658mqvoZ2ne-rRA00OozuDwl4v4kTwieDIGahhocfEfhYPExenzIXZMyE_dai9kh44cZgWCAhCLrjEI4dTkOofOd9Dmv2u98Gzh6PvVAUthW4qS5QFDH-X9qWep1LjfnaOAvLJlVyuPPT_n9Eqa1veKBiQ52Y__ugQEpTBzVlQmNdzcjBPamNCxl5XEtGh4N2fQ",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJaNkF0NnhoU2lCOTQtdEItcHZfT1VHeUdFbXVRbzFhWWwwcXczSFFTZE5zIn0.eyJqdGkiOiIwYWI2NjRmYS01NjhiLTRiNmEtYWVhZS1hMTRmZDBlNWRhZjAiLCJleHAiOjE1MDcxNjU4MDUsIm5iZiI6MCwiaWF0IjoxNTA3MTY0MDA1LCJpc3MiOiJodHRwczovL2tleWNsb2Frc3NvLmdwbS5zb2x1dGlvbnM6ODQ0My9hdXRoL3JlYWxtcy9xdWlja3N0YXJ0IiwiYXVkIjoiYXBwLXByb2ZpbGUtdmFuaWxsYSIsInN1YiI6IjhhYjY3YzcyLWRkNTUtNGU3Mi05NmUxLWE0NDYwZGQ3OTRkZiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhcHAtcHJvZmlsZS12YW5pbGxhIiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiMTM1NGNmYzMtN2Q1Yy00OTk4LThkYmItZWRjOGM3MjlmZDE4IiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicmVhbG0tbWFuYWdlbWVudCI6eyJyb2xlcyI6WyJ2aWV3LXJlYWxtIiwidmlldy1pZGVudGl0eS1wcm92aWRlcnMiLCJtYW5hZ2UtaWRlbnRpdHktcHJvdmlkZXJzIiwiaW1wZXJzb25hdGlvbiIsInJlYWxtLWFkbWluIiwiY3JlYXRlLWNsaWVudCIsIm1hbmFnZS11c2VycyIsInF1ZXJ5LXJlYWxtcyIsInZpZXctYXV0aG9yaXphdGlvbiIsInF1ZXJ5LWNsaWVudHMiLCJxdWVyeS11c2VycyIsIm1hbmFnZS1ldmVudHMiLCJtYW5hZ2UtcmVhbG0iLCJ2aWV3LWV2ZW50cyIsInZpZXctdXNlcnMiLCJ2aWV3LWNsaWVudHMiLCJtYW5hZ2UtYXV0aG9yaXphdGlvbiIsIm1hbmFnZS1jbGllbnRzIiwicXVlcnktZ3JvdXBzIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19fQ.GMq3_uYr_1J1BRQi_SVB95A4H-ehKf3Q_wHp3DlzlMq4dHdljsS81PLAkbX9Wf64_6uHZ5h-Se0yP8d8Ta9WYvI9QFsPrl6JYI3WDtCYXuxC2Eoz9sW0hOqN3tISqGVaptvF8bWOAysvL4D9sxIQLm-lJnPqSKl9QKUejoTLInjkj-Ur5GEhE172-z8PgVQRNxILQ5XIGJZKoL8PoHhd88JpdUhfPjUknlOOJYei0EG2YHfyeRXO-s8LFED0m2PWP_BMTUooytWLD0wvbPwOx9vjV_vNyyLuxMmVUZOzy-zZ67NQSxOlAJF0en32578fbXWHy8eORxVncWzrdpgeYw",
"token_type": "bearer",
"not-before-policy": 1507091629,
"session_state": "1354cfc3-7d5c-4998-8dbb-edc8c729fd18"
}

 

If you copy the access_token, for instance, and paste it to the http://jwt.io debugger, you're going to see something like that:

 

 

Well, so far nothing extraordinary. Just a different way to present the tokens. My challenge was to validate the signature of the JWT, once the gateway had to do that in order to pass it through to call some APIs in the customer's backend. I had a hard time trying to understand why the provided public key wasn't working with our Decode Json Web Token assertion. The weird thing is that it was working with the jwt.io debugger but not within my policy. The trick for me was to use Json Web Key (JWK) to validate the JWT signature. The JWK is usually meant to provide a way to do that validation offline. After spending some time digging the Keycloak documentation I found how to get that information out of the Keycloak. All you have to do is to point your browser to the correct URL. Make sure you use the correct realm when doing that. For example, this is mine:

 

https://keycloaksso.gpm.solutions:8443/auth/realms/quickstart/protocol/openid-connect/certs 

 

As you can see, quickstart is the realm we are using in this lab. This endpoint will return the JWK Set, which is a Json like this:

 

{
"keys": [
{
"kid": "Z6At6xhSiB94-tB-pv_OUGyGEmuQo1aYl0qw3HQSdNs",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "iZ_tu0KPgxV93BHgkOZrPyC1uKVw-1gC5U2DGaqO5C-pG02QK-GfZPVrt7mR_7bfiMkOqv_Fw2vZZsjN_Pr-D9qZHZuvYxm7jq4OokterwiXxEQ9-jzcue2rEqcepuaYU35j0UH_bOBZ5Ro_boz5TGYs3EP5F41mJAWq2SAv4ykAei151mtYnuFDnylS5SUQLqEgbKg1UT1unmhsSnN0Oq8pwGMtqYSo5pDzULUv6ojwLDj571ttmqspRMgP2Hy7FCDmnW_gBgUIE7vFMW1QjeY1vUwvJGSUlSFUy7MR4ySNlRvYudiOPG5DZWCbkbb4VFwwYaMGzSXfXIE9xVBLzw",
"e": "AQAB"
}
]
}

 

Now the policy I did to validate the JWT. This endpoint is expecting a header named JWT with the token to be validated. This is what I did and how I tweaked the assertion:

 

The most important thing here is the assertion #62 and its configuration:

 

The trick is to use the Key ID, from the JWK Set (look the previourly posted Json snippet and look for the red highlighted key), and use it here. Notice that the JWK is inside a variable too.

 

I attached the policy I did in case you wanted to give it a try

 

I hope this helps and can save you some time!

Statistics
0 Favorited
7 Views
1 Files
0 Shares
4 Downloads
Attachment(s)
zip file
decode-jwt-keycloak.xml.zip   9 KB   1 version
Uploaded - May 29, 2019

Related Entries and Links

No Related Resource entered.