marcospereirampj / python-keycloak

MIT License
728 stars 303 forks source link

Can't get policies: audience error. #402

Open rcapp opened 1 year ago

rcapp commented 1 year ago

Hello, everyone!

I'm trying to use the get_policies function, but I'm getting errors related with the Audience. I cannot find where in Keycloak I set them, so I'm not able to see if they are doubled (one of the errors says there are too many audiences). It maybe a problem with my configuration, but could be a bug too. Nevertheless, here is how I'm doing it:


import jwt  # PyJWT Lib
import requests

from jwt.algorithms import RSAAlgorithm

token = keycloak_openid.token("test", "1q2w3e4r")
keys_endpoint = requests.get("http://172.20.0.4:8080/realms/demo/protocol/openid-connect/certs")
key_jwt = next(
        (kj for kj in keys_endpoint.json()["keys"] if kj["kid"] == "[key-removed]"), None
    )
public_key = RSAAlgorithm.from_jwk(json.dumps(key_jwt))
decoded = jwt.decode(token["id_token"], public_key, audience="demo-api", algorithms="RS256")  # it outputs fine

public_key = "-----BEGIN PUBLIC KEY-----\n" + keycloak_openid.public_key() + "\n-----END PUBLIC KEY-----"
keycloak_openid.load_authorization_config("config.json")
policies = keycloak_openid.get_policies(token["access_token"], audience="demo-api", method_token_info="decode", key=public_key)  # it crashes

These are the errors:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[76], line 1
----> 1 policies = keycloak_openid.get_policies(token["access_token"], method_token_info='decode', audience="buyer-api", key=key_decoded)

File ~/sandbox/keycloak/demo/venv/lib/python3.8/site-packages/keycloak/keycloak_openid.py:558, in KeycloakOpenID.get_policies(self, token, method_token_info, **kwargs)
    553 if not self.authorization.policies:
    554     raise KeycloakAuthorizationConfigError(
    555         "Keycloak settings not found. Load Authorization Keycloak settings."
    556     )
--> 558 token_info = self._token_info(token, method_token_info, **kwargs)
    560 if method_token_info == "introspect" and not token_info["active"]:
    561     raise KeycloakInvalidTokenError("Token expired or invalid.")

File ~/sandbox/keycloak/demo/venv/lib/python3.8/site-packages/keycloak/keycloak_openid.py:216, in KeycloakOpenID._token_info(self, token, method_token_info, **kwargs)
    214     token_info = self.introspect(token)
    215 else:
--> 216     token_info = self.decode_token(token, **kwargs)
    218 return token_info

File ~/sandbox/keycloak/demo/venv/lib/python3.8/site-packages/keycloak/keycloak_openid.py:526, in KeycloakOpenID.decode_token(self, token, key, algorithms, **kwargs)
    503 def decode_token(self, token, key, algorithms=["RS256"], **kwargs):
    504     """Decode user token.
    505 
    506     A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data
   (...)
    524     :rtype: dict
    525     """
--> 526     return jwt.decode(token, key, algorithms=algorithms, audience=self.client_id, **kwargs)

TypeError: decode() got multiple values for keyword argument 'audience'

The two commands bellow they also crashes:

policies = keycloak_openid.get_policies( ..., audience="account", ...) # same error as above

and without the audience parameter it crashes with the error bellow:

---------------------------------------------------------------------------
JWTClaimsError                            Traceback (most recent call last)
Cell In[109], line 1
----> 1 policies = keycloak_openid.get_policies(token["access_token"],  method_token_info="decode", key=public_key)  # it crashes too

File ~/sandbox/keycloak/demo/venv/lib/python3.8/site-packages/keycloak/keycloak_openid.py:558, in KeycloakOpenID.get_policies(self, token, method_token_info, **kwargs)
    553 if not self.authorization.policies:
    554     raise KeycloakAuthorizationConfigError(
    555         "Keycloak settings not found. Load Authorization Keycloak settings."
    556     )
--> 558 token_info = self._token_info(token, method_token_info, **kwargs)
    560 if method_token_info == "introspect" and not token_info["active"]:
    561     raise KeycloakInvalidTokenError("Token expired or invalid.")

File ~/sandbox/keycloak/demo/venv/lib/python3.8/site-packages/keycloak/keycloak_openid.py:216, in KeycloakOpenID._token_info(self, token, method_token_info, **kwargs)
    214     token_info = self.introspect(token)
    215 else:
--> 216     token_info = self.decode_token(token, **kwargs)
    218 return token_info

File ~/sandbox/keycloak/demo/venv/lib/python3.8/site-packages/keycloak/keycloak_openid.py:526, in KeycloakOpenID.decode_token(self, token, key, algorithms, **kwargs)
    503 def decode_token(self, token, key, algorithms=["RS256"], **kwargs):
    504     """Decode user token.
    505 
    506     A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data
   (...)
    524     :rtype: dict
    525     """
--> 526     return jwt.decode(token, key, algorithms=algorithms, audience=self.client_id, **kwargs)

File ~/sandbox/keycloak/demo/venv/lib/python3.8/site-packages/jose/jwt.py:157, in decode(token, key, algorithms, options, audience, issuer, subject, access_token)
    154 if not isinstance(claims, Mapping):
    155     raise JWTError("Invalid payload string: must be a json object")
--> 157 _validate_claims(
    158     claims,
    159     audience=audience,
    160     issuer=issuer,
    161     subject=subject,
    162     algorithm=algorithm,
    163     access_token=access_token,
    164     options=defaults,
    165 )
    167 return claims

File ~/sandbox/keycloak/demo/venv/lib/python3.8/site-packages/jose/jwt.py:484, in _validate_claims(claims, audience, issuer, subject, algorithm, access_token, options)
    481     _validate_exp(claims, leeway=leeway)
    483 if options.get("verify_aud"):
--> 484     _validate_aud(claims, audience=audience)
    486 if options.get("verify_iss"):
    487     _validate_iss(claims, issuer=issuer)

File ~/sandbox/keycloak/demo/venv/lib/python3.8/site-packages/jose/jwt.py:350, in _validate_aud(claims, audience)
    348     raise JWTClaimsError("Invalid claim format in token")
    349 if audience not in audience_claims:
--> 350     raise JWTClaimsError("Invalid audience")

JWTClaimsError: Invalid audience

Any help is appreciated! Cheers!

rcapp commented 1 year ago

Quick update: I changed how the library behaves when checking the audience, haven't done any real work to test and validate. So I won't call it a "solution". Also, I'm not sure how much this change bellow would impact the rest of the library:

    def decode_token(self, token, key, algorithms=["RS256"], **kwargs):
        """Decode user token. """

        return jwt.decode(token, key, algorithms=algorithms, **kwargs)        

Then, I provide the intended audience (account) for the access_token, since I cannot pass a ID Token to use the client audience.

https://github.com/keycloak/keycloak/discussions/14398 -- this is more or less my situation and one of the replies mention the client_id should be used on ID Token, not on access_token.

Cheers!

kantorv commented 1 year ago

found a workaround here https://github.com/marcospereirampj/python-keycloak/issues/89#issuecomment-1497759963

NdSaid commented 1 year ago

The error you're encountering seems to be related to how you are decoding and validating a JWT token using the PyJWT library in Python. The error messages are indicating problems with the audience (aud) claim in the JWT. The issue appears to be in the validation of the audience, which doesn't match what you expected.

To resolve this issue, follow these steps:

1.Review Audience (aud) Claim: The error messages suggest that there is a problem with the audience claim in the JWT. Verify that the audience you are providing to the jwt.decode method ("demo-api") matches the audience in the token. In your code, it seems you're expecting the audience to be "demo-api".

2.Check Token Payload: Print the contents of the token["id_token"] to see what audience is included in the token. Make sure the audience in the token matches what you're using in the jwt.decode method. Verify that the aud claim in the decoded token matches the expected audience ("demo-api").

3.Ensure Proper Token Signing Algorithm: Ensure that the token you are trying to decode (token["id_token"]) is signed using the RS256 algorithm since you are specifying algorithms="RS256" when calling jwt.decode. If the token uses a different signing algorithm, you will need to adjust the algorithm parameter accordingly.

Check Token Issuer (iss) Claim: Additionally, you should verify that the issuer (iss) claim of the token ("iss") matches your Keycloak server's URL. You can add this check using the issuer parameter when calling jwt.decode.

Verify Token in Keycloak: Manually verify the token in your Keycloak admin console to confirm the audience claim and other token attributes.