Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KeyCloak decoding with public_key in python

I did not know how to decode the result from keycloak public key and the certs. Which one should I use to decode? I only have access_token. And have been try with the cert and public_key

Result from : https://keycloak.some.domain/auth/realms/name-realm/

{
"realm": "name-realm",
"public_key": "some-secert-stringMIIBIsome-secert-stringknhFmdCmX9lu1EJNEsome-secert-string",
"token-service": "https://keycloak.some.domain/auth/realms/name-realm/protocol/openid-connect",
"account-service": "https://keycloak.some.domain/auth/realms/name-realm/account",
"tokens-not-before": 0
}

Result from : https://keycloak.some.domain/auth/realms/epf-uat/protocol/openid-connect/certs

{
"keys": [
{
"kid": "WtJZKhwIsome-secert-stringA",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "xtG3QzVml8lxYQz1FaesgZ2-TPR2h_NqGHwRsome-secert-stringH2Bd5Dncsome-secert-stringEHVBAd75gzIPh_wTsome-secert-stringiAw",
"e": "AQAB",
"x5c": [
"some-secert-string"
],
"x5t": "y-Ksome-secert-stringMViQ",
"x5t#S256": "vvsome-secert-stringbosome-secert-stringtE8QW2vnmw60NJfaDJlVE"
}
]
}

I have try it using https://github.com/mpdavis/python-jose to decode the jwt. here the sample:

Setting:

keycloak_algorithm = ["RS256"]
keycloak_domain = "https://keycloak.some.domain/auth/realms/some-realm/"
keycloak_audience = "https://login.some-domain.com"

Auth.py

token = CLEANED_BEARER_TOKEN_FROM_CLIENT
jsonurl = urlopen(setting.keycloak_domain)
algorithms = setting.keycloak_algorithm
audience = setting.keycloak_audience
issuer = setting.keycloak_domain

jwks = json.loads(jsonurl.read())

try:
    payload = jwt.decode(token, jwks["public_key"], algorithms=algorithms, audience=audience, issuer=issuer)
except jwt.ExpiredSignatureError:
   raise AuthError("Token is expired. Please update your token.", 401)
except jwt.JWTClaimsError as e:
   raise AuthError("Invalid claims. " + str(e), 401)
except jwt.JWTError as e:
    raise AuthError("JWT Error." + str(e), 401)
except Exception as e:
    raise AuthError("Unable to parse authentication token. " + str(e), 401)

Here the result if I use the domain (without protocol/openid-connect/certs endpoints in ):

AuthError: ('Unable to parse authentication token. Could not deserialize key data.', 401)

And here if I use the protocol/openid-connect/certs endpoints:

AuthError: ('JWT Error.Signature verification failed.', 401)

I Have no Idea why this 2 things (public_key and certs) can not decoded.

Other notes:

  • I am using FastApi (https://fastapi.tiangolo.com/)

  • There is working part with express js. So I only consume auth from express js with python app. (express js will hit the endpoint by provide the access_code)

like image 765
Ilham Bintang Avatar asked Sep 07 '26 22:09

Ilham Bintang


1 Answers

The public_key seems to be in a base64-encoded DER format. You should be able to use it like this:

from base64 import b64decode

import jwt
from cryptography.hazmat.primitives import serialization

r = requests.get("https://keycloak.some.domain/auth/realms/name-realm/")
r.raise_for_status()
key_der_base64 = r.json()["public_key"]
key_der = b64decode(key_der_base64.encode())

public_key = serialization.load_der_public_key(key_der)

payload = jwt.decode(token, public_key, algorithms=["RS256"])

However, note that the https://keycloak.some.domain/auth/realms/name-realm/ endpoint does not seem to follow any formal standard. You might be better off using the /certs endpoint, as illustrated here:

import jwt
from jwt import PyJWKClient

url = "https://keycloak.some.domain/auth/realms/epf-uat/protocol/openid-connect/certs"
jwks_client = PyJWKClient(url)
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload = jwt.decode(token, signing_key.key, algorithms=["RS256"])
like image 126
Martin Cejp Avatar answered Sep 10 '26 11:09

Martin Cejp



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!