6

I am using Keycloak to handle login and generate JWT tokens. I need to be able to verify the access token that I'm sending to my REST API service. Best practice is to use the JWT secret to verify the token directly rather than send it to the Keycloak server for verification. There are a lot of Java examples of doing this, but I need to be able to verify this using python or ruby.

I tried the following python signature verification but I get an error of ValueError: Could not unserialize key data. I also tried entering the public key in the https://jwt.io debugger but also get an invalid signature.

#!/usr/bin/env python3

import jwt

# Public key from Keycloak realm -> Keys -> Public Key -> (view)
public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu77nUtVw7SIIcUTSiStzMPB7BGB/9eS+CpppsUaiyZyWCXlrALT3YdqneSlpX4Ta+0wvhOkKQtoSS8dCH8GIi7esAmfdHetHfRgeDXHAlXo8HIzshUzODg3ysT7j+Ha3eJsO+LNS/omHDhsarP8Z2eThW876iKJCCc/mB76a6u1e4Id+52K5lG++m8Pn4Gs+cqd2sKUKcMJ9CkJ6dBIdGlXHMoOHj4C33SPrEG/vEBv5cu0l5PP3RiBAuaZHpLKzfIiaLOpj/k4dD/weVt5gwTIJn16AEgPD7173Xef0HgoPlQInDFrJwsGpYCnIPZWSxRbvjKkya2Auj0QZyMCrXwIDAQAB"

# Keycloak JWT RS256 access-token
access_token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0LVlJOUlVc2R6NGM0SHoycXczT0xXZ0I0eHc2eFd4T29XdktVT2FvV3FzIn0.eyJqdGkiOiJjYzZkMjM5OS04ZmU5LTQzMWItYjZjMS05NWQxMWUxN2FiZGQiLCJleHAiOjE0ODE1NTQ2NTksIm5iZiI6MCwiaWF0IjoxNDgxNTU0MzU5LCJpc3MiOiJodHRwczovL3BtaS1rZXljbG9hay5zYnZpbXByb3Zlci5jb20vYXV0aC9yZWFsbXMvT3BlbkJFTCIsImF1ZCI6ImJlbG1nciIsInN1YiI6ImU1ODc2OGQxLWU3ODktNGU3Yi04ZGVlLWJjMzYxNzFkZDNhZCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImJlbG1nciIsIm5vbmNlIjoiMzc2ZDdjZmUtZGZkNC00Yzg5LWJlZGEtZTlmOWNmYWNlMTNkIiwiYXV0aF90aW1lIjoxNDgxNTUzMTY0LCJzZXNzaW9uX3N0YXRlIjoiN2E2OTEzYzItZWJkZC00MTc2LWI4YTAtZDc2NzVhNDZkNmJjIiwiYWNyIjoiMSIsImNsaWVudF9zZXNzaW9uIjoiOTMyNGMzMjAtMmE4Zi00ODBlLTg5MjItZGQxNmFmMDQxZDdmIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsInZpZXctcHJvZmlsZSJdfX0sIm5hbWUiOiJXaWxsaWFtIEhheWVzIiwicHJlZmVycmVkX3VzZXJuYW1lIjoid2lsbGlhbS5zLmhheWVzQGdtYWlsLmNvbSIsImdpdmVuX25hbWUiOiJXaWxsaWFtIiwiZmFtaWx5X25hbWUiOiJIYXllcyIsImVtYWlsIjoid2lsbGlhbS5zLmhheWVzQGdtYWlsLmNvbSJ9.Q7s-qTcJyH69Ebof8pQI1kZzeT8olwQnRJ06uas5TP2isacxOheHnJ9ixEvqTrr-iefmYMwx41jM68NCs6l8IBNHqv7t5-ediizx4ianMiXr7oZ_1oAT9hkLyrpv9iF2IZBtzNJz0GQAnDYe1moLOLuzqwvcUaWgmzRY95xvzo4kbE8OkeZiMpD_cDmp3_vKOsdn3B6ybJ9TXtea55A29pQzsvAM_6lHeyxTCisipOtu_ubnUOamkYSpxLwWZXgI1w7iz-igt-n7xtlFhUpra239yn9uly9iuBtlgnc3TFDmZn-XRq_PODDJNJeaQXDRaDqnRQhXsoObxCaPqXDQ3A"

access_token_json = jwt.decode(access_token, public_key)
print(access_token_json)
William
  • 705
  • 1
  • 6
  • 17

3 Answers3

10

To verify the access token I did the following things:

  • I reset the expiration timeframe so I didn't have to worry about timeouts as a complicating factor.

  • I had to add the BEGIN/END header/footer to the pubkey (of which there are two different versions based on the encoding of the pubkey -e.g. 'BEGIN RSA PUBLIC KEY' is not the right version for the Keycloak pubkey encoding):

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu77nUtVw7SIIcUTSiStzMPB7BGB/9eS+CpppsUaiyZyWCXlrALT3YdqneSlpX4Ta+0wvhOkKQtoSS8dCH8GIi7esAmfdHetHfRgeDXHAlXo8HIzshUzODg3ysT7j+Ha3eJsO+LNS/omHDhsarP8Z2eThW876iKJCCc/mB76a6u1e4Id+52K5lG++m8Pn4Gs+cqd2sKUKcMJ9CkJ6dBIdGlXHMoOHj4C33SPrEG/vEBv5cu0l5PP3RiBAuaZHpLKzfIiaLOpj/k4dD/weVt5gwTIJn16AEgPD7173Xef0HgoPlQInDFrJwsGpYCnIPZWSxRbvjKkya2Auj0QZyMCrXwIDAQAB
-----END PUBLIC KEY-----
  • Last - I had to add the audience='belmgr' to the jwt.decode for the python script
## Python script 
#!/usr/bin/env python3
    
import jwt
    
public_key = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu77nUtVw7SIIcUTSiStzMPB7BGB/9eS+CpppsUaiyZyWCXlrALT3YdqneSlpX4Ta+0wvhOkKQtoSS8dCH8GIi7esAmfdHetHfRgeDXHAlXo8HIzshUzODg3ysT7j+Ha3eJsO+LNS/omHDhsarP8Z2eThW876iKJCCc/mB76a6u1e4Id+52K5lG++m8Pn4Gs+cqd2sKUKcMJ9CkJ6dBIdGlXHMoOHj4C33SPrEG/vEBv5cu0l5PP3RiBAuaZHpLKzfIiaLOpj/k4dD/weVt5gwTIJn16AEgPD7173Xef0HgoPlQInDFrJwsGpYCnIPZWSxRbvjKkya2Auj0QZyMCrXwIDAQAB
-----END PUBLIC KEY-----"""
    
access_token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0LVlJOUlVc2R6NGM0SHoycXczT0xXZ0I0eHc2eFd4T29XdktVT2FvV3FzIn0.eyJqdGkiOiJiMGFhZDllMC1lYmU5LTQ4ZDQtYTcxNC1iMWMyNjg4NmM2MDciLCJleHAiOjE0ODE5ODE0ODQsIm5iZiI6MCwiaWF0IjoxNDgxODk1MDg0LCJpc3MiOiJodHRwczovL3BtaS1rZXljbG9hay5zYnZpbXByb3Zlci5jb20vYXV0aC9yZWFsbXMvT3BlbkJFTCIsImF1ZCI6ImJlbG1nciIsInN1YiI6ImU1ODc2OGQxLWU3ODktNGU3Yi04ZGVlLWJjMzYxNzFkZDNhZCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImJlbG1nciIsIm5vbmNlIjoiMzI3NjRhYWUtZmM1NC00MDlkLTgxM2EtNjhhNmM4YTNhYzI2IiwiYXV0aF90aW1lIjoxNDgxODk1MDgzLCJzZXNzaW9uX3N0YXRlIjoiMWQwNDMyMDQtNWNkYy00OTVjLWJlZWUtODIwZWJiMmRlNWUzIiwiYWNyIjoiMSIsImNsaWVudF9zZXNzaW9uIjoiMmEzZWY1MzgtN2MxOS00YzE3LTlmZTctYjQ3ZGNjNmM0ODQyIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsInZpZXctcHJvZmlsZSJdfX0sIm5hbWUiOiJXaWxsaWFtIEhheWVzIiwicHJlZmVycmVkX3VzZXJuYW1lIjoid2lsbGlhbS5zLmhheWVzQGdtYWlsLmNvbSIsImdpdmVuX25hbWUiOiJXaWxsaWFtIiwiZmFtaWx5X25hbWUiOiJIYXllcyIsImVtYWlsIjoid2lsbGlhbS5zLmhheWVzQGdtYWlsLmNvbSJ9.DfC1c6BVBZ8Bgwu6CYGBsWp4T0dqltwAQ84E1Q0LdjFxvtVeDOF8rBIdgkr7rMCHObZWsEotljSR4BZzCvfDNmdk_25sedvi-ZHXTP0-nSeHczIXBstZ8p257A6-fEiIcG5CRoClHMI317bVGjNkzAV7l8kuBhr0bfrDedxpvKo3EQah4MrOF7-JXQGPAWlLDV1E9zsrT99Vm_XL58M-ur8q7N-B-CmOBV2GGsMEosTDK_-U-mattEN6PMNiG004Ryg0iPDM4-kr1AQsPE_wHBYf81_-vrqs7ec--0ShJYdC8-eBbuf9xVixNQVPRl7mnktaKA19YXdzdCwcQa6crw"
    
access_token_json = jwt.decode(access_token, public_key, audience='belmgr')
print(access_token_json)
Tiago Martins Peres
  • 14,289
  • 18
  • 86
  • 145
William
  • 705
  • 1
  • 6
  • 17
  • 1
    Did You manage to use Keycloak in your micro-services. I am facing the same issue. My Client is authenticating user from Keycloak server and send `access_token` to micro-service (written in Python). Now here I am confused, how to verify that `access_token` , get user info from that token ? – user5594493 Jul 20 '17 at 09:11
  • 1
    I did manage to use the access token in my micro-services and other services. You should be able to copy the code I have above - install PyJWT `pip install PyJWT` and run that code above. The access_token_json will have the payload of the JWT in it. Just dump it out using pprint or json.dumps – William Jul 21 '17 at 11:14
5

I want to add to this knowledge in case anyone else is stuck on this.

The public_key value copied straight from Keycloak realm -> Keys -> Public Key -> (view) did not work. Instead, I had to get the public key that was exposed by Keycloak for the realm by using the URL:

https://<< my keycloak url >>/auth/realms/<< my realm >>/

This url returns json that includes a value for "public_key" for the realm "my realm". Using THIS value of the public key in the way that's been suggested in the selected answer worked.

However, you need to also make sure your audience value is correct with the decode call. For my use, the value "belmgr". I found the audience I needed by using jwt.io, decoding the access_token, and in the payload data, I found:

{ ..."aud": "account",... }

Using "account" as the audience worked.

# wrong audience - FAIL
access_token_json = jwt.decode(access_token, public_key, audience='belmgr')

# right audience - SUCCESS
access_token_json = jwt.decode(access_token, public_key, audience='account')
Chris
  • 171
  • 2
  • 8
0

A better way to validate the bearer token from keycloak is to use the decode_token method of KeycloakOpenID client.

from keycloak.keycloak_openid import KeycloakOpenID
KEYCLOAK_OPENID_CLIENT = KeycloakOpenID(server_url='keycloak_base_url',
                                                client_id='client_id',
                                                client_secret_key='client_secret',
                                                realm_name='realm_name'
                                                )
KEYCLOAK_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" + KEYCLOAK_OPENID_CLIENT.public_key() + "\n-----END PUBLIC KEY-----"
options = {"verify_signature": True,"verify_aud": True, "verify_exp": True}
KEYCLOAK_OPENID_CLIENT.decode_token(access_token, key=KEYCLOAK_PUBLIC_KEY, options=options)

python package for client --https://pypi.org/project/python-keycloak/

NB: Please be sure to add your audience claim properly. For more details visit post Keycloak-gatekeeper: 'aud' claim and 'client_id' do not match