jpadilla / pyjwt

JSON Web Token implementation in Python
https://pyjwt.readthedocs.io
MIT License
5k stars 675 forks source link

JWT decode requires audience #870

Closed ddtxra closed 1 year ago

ddtxra commented 1 year ago

Hello, I tried to use the documentation found in here: https://pyjwt.readthedocs.io/en/latest/usage.html#retrieve-rsa-signing-keys-from-a-jwks-endpoint related to the validation of a JWT token using JWKS but it was not working for me using Keycloak issuer.

Then I searched for more documentation on Google and found out this great blog post: https://renzolucioni.com/verifying-jwts-with-jwks-and-pyjwt/

At the end I wrote down this code which worked for me (and could be useful for someone else, so I paste it here)

    token_response = oauth.keycloak.authorize_access_token()
    id_token = oauth.keycloak.parse_id_token(token_response)

    # Reads the keys from JWK and creates a dictionary with the RSAPublic keys
    jwk_uris = requests.get(f'{issuer}/.well-known/openid-configuration').json()["jwks_uri"]
    jwks = requests.get(jwk_uris).json()

    public_keys = {}
    for jwk in jwks['keys']:
        kid = jwk['kid']
        public_keys[kid] = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk))

    if id_token:
        logging.debug('logged in ' + id_token['email'])
        session['user'] = id_token
        token = token_response['access_token']
        # get the identifier of the key used from the header token
        kid = jwt.get_unverified_header(token)['kid']
        # gets the key associated
        key = public_keys[kid]
        try:
            session['resource_access'] = jwt.decode(token, key=key, audience="app_****", algorithms=["RS256"])['resource_access']
        except jwt.exceptions.MissingRequiredClaimError as exc:
            session['resource_access'] = {}

But at the end I still have problem with this code. Why do I need to specify an audience? Some of my users don't have the required resource_access for the specified audience, so the token does not contain the "aud" field, which is ok. But when those users try to login, the decode function crashes with MissingRequiredClaimError. Is there a way to specify all audiences or ignore this field? It seems mandatory to set in the decode function and also the token must contain the "aud" field...

Viicos commented 1 year ago

I tried to use the documentation found in here: https://pyjwt.readthedocs.io/en/latest/usage.html#retrieve-rsa-signing-keys-from-a-jwks-endpoint related to the validation of a JWT token using JWKS but it was not working for me using Keycloak issuer.

That may be because of https://github.com/jpadilla/pyjwt/pull/863, your JWK endpoint might be returning unsupported keys and PyJWT currently fails to ignore them.

But at the end I still have problem with this code. Why do I need to specify an audience? Some of my users don't have the required resource_access for the specified audience, so the token does not contain the "aud" field, which is ok. But when those users try to login, the decode function crashes with MissingRequiredClaimError. Is there a way to specify all audiences or ignore this field? It seems mandatory to set in the decode function and also the token must contain the "aud" field...

By default, even if you don't pass any value to the audience parameter of decode, PyJWT will validate the aud claim, if present in the token. Validation will succeed if there's no aud claim in the token.

But as you are explicitly setting the audience to app_****, PyJWT will raise an error even if no aud claim is available in the token.

To disable the aud validation, use:

jwt.decode(token, key=key, algorithms=["RS256"], options={"verify_aud": False"})
github-actions[bot] commented 1 year ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days

Viicos commented 1 year ago

as https://github.com/jpadilla/pyjwt/pull/863 is now fixed, this can be closed.