ddtxra
ddtxra

Reputation: 55

JWT decode requires audience #870

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...

Upvotes: 2

Views: 6976

Answers (2)

Andreas
Andreas

Reputation: 121

Exactly like Sai Ramachandran wrote, try:

header = jwt.get_unverified_header(token)
alg = header["alg"]
kid = header["kid"]

jwk_client = jwt.PyJWKClient(settings.OIDC_OP_JWKS_ENDPOINT)
key = jwk_client.get_signing_key(kid).key
token_decoded = jwt.decode(
    token,
    key,
    algorithms=[alg],
    options={"verify_aud": False, "verify_signature": True},
)

Upvotes: 4

ddtxra
ddtxra

Reputation: 55

Thank you for your answer @jps.

When setting verify_aud=False in the decode function with a token without an audience. I get "Token is missing the "aud" claim"

# token does not contain an audience. -> "Token is missing the "aud" claim"
token_decoded = jwt.decode(token, key=key, audience="app_****", verify_aud=False, algorithms=["RS256"])

If I try to decode without specifiy the audience in the decode function, for a token which does not contain the audience it works!

# token does not contain an audience.  -> Works!
token_decoded = jwt.decode(token, key=key, verify_aud=False, algorithms=["RS256"])

However if my token contains the audience and I try to decode with the same function I get an error: InvalideAudienceError: Invalid audience

# token contains the audience ->  InvalideAudienceError: Invalid audience
token_decoded = jwt.decode(token, key=key, verify_aud=False, algorithms=["RS256"])

So it looks like I have to decode the token without verifying its signature to check if there is an audience in the token. Depending if there is an audience I have then to decode with signature verification by calling A or B. It does not make too much sense, does it?

A) For token with an audience

jwt.decode(token, key=key, audience="app_****", algorithms=["RS256"])

B) For tokens without an audience

token_decoded = jwt.decode(token, key=key, verify_aud=False, algorithms=["RS256"])

Upvotes: 0

Related Questions