3

I need to validate a azure jwt access_token from the service I'm working on. We are currently making a request to https://graph.microsoft.com/beta/me. If the request succeeded, the token is valid. Unfortunately we will not be able to keep doing that.

I have tried a variety of ideas for this. None of them with success. Even jwt.io does not recognize the signature, even though jwt kid and the kid from one of the available signatures in jwk_uri matches.

Based on this blog post I have created a following solution (also available on github) .

My implementation is very similar to the blog post with a few changes:

#!/usr/bin/env python2

import jwt
import requests
import sys

from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend


def get_public_key(access_token):
    """ Retrieve public key for access token """
    token_header = jwt.get_unverified_header(access_token)

    res = requests.get('https://login.microsoftonline.com/common/.well-known/openid-configuration')
    jwk_uri = res.json()['jwks_uri']

    res = requests.get(jwk_uri)
    jwk_keys = res.json()

    x5c = None
    # Iterate JWK keys and extract matching x5c chain
    for key in jwk_keys['keys']:
        if key['kid'] == token_header['kid']:
            x5c = key['x5c']
            break
    else:
        raise Exception('Certificate not found in {}'.format(jwk_uri))

    cert = ''.join([
        '-----BEGIN CERTIFICATE-----\n',
        x5c[0],
        '\n-----END CERTIFICATE-----\n',
    ])
    try:
        public_key =  load_pem_x509_certificate(cert.encode(), default_backend()).public_key()
    except Exception as error:
        raise Exception('Failed to load public key:', error)

    return public_key, key['kid']

def main():
    print '\n'
    if len(sys.argv) < 2 or '-h' in sys.argv:
        print 'Run it again passing acces token:\n\tpython jwt_validation.py <access_token>'
        sys.exit(1)

    access_token = sys.argv[1]
    audience = 'https://graph.microsoft.com'

    public_key, kid = get_public_key(access_token)

    try:
        jwt.decode(
            access_token,
            public_key,
            algorithms='RS256',
            audience=audience,
        )
    except Exception as error:
        print 'key {} did not worked, error:'.format(kid), error
        sys.exit(1)

    print('Key worked!')

if __name__ == '__main__':
    main()

This solution returns Invalid Signature for a valid access_token with the following traceback:

Traceback (most recent call last):
File "jwt_validation/jwt_validation.py", line 63, in <module>
    main()
File "jwt_validation/jwt_validation.py", line 57, in main 
    audience=audience,
File "~/.pyenv/versions/jwt-validation-tool/lib/python2.7/site-packages/jwt/api_jwt.py", line 93, in decode
    jwt, key=key, algorithms=algorithms, options=options, **kwargs
File "~/.pyenv/versions/jwt-validation-tool/lib/python2.7/site-packages/jwt/api_jws.py", line 157, in decode
    key, algorithms)
File "~/.pyenv/versions/jwt-validation-tool/lib/python2.7/site-packages/jwt/api_jws.py", line 224, in _verify_signature
    raise InvalidSignatureError('Signature verification failed')
jwt.exceptions.InvalidSignatureError: Signature verification failed

Any ideas of what I could be wrong would be helpful.

2
  • 1
    Could you include the error message here as well? In jwt documentation, they expect the algorithms value as a list. Not sure if thats the issue here. Commented Nov 28, 2018 at 3:50
  • Thanks @RakihthaRR for the suggestion. Traceback added. The value algorithms='RS256' can be a list as you mention, but it also works as a string. Commented Nov 29, 2018 at 19:59

2 Answers 2

5

I ran into simillar issue, after couple of days investigating I've found in my case that I am requesting token with built in scopes (openid and profile) and without any custom scope which results in issuing tokens with different audiance (MS Graph) and hence tokens signed with with different public key (because I think that issued access_token is just a forward of the delegated MS Graph scope).

I resolved the issue by adding a custom scope in my app registration (Expose API section) and now my access_tokens are issued with valid audiance and I am able to check signature with my app public key.

Sign up to request clarification or add additional context in comments.

Comments

1

Usually the audience claim is used to point to the client_id of the client application you have registered in Azure/Microsoft. Looks to me that the error is occurring due to a mismatch of claims in jwt(probably audience). Check whether you have set the correct client_id for the audience variable.

8 Comments

This suggestion make sense and I have tried that too without success. Looking into the debugger I confirmed. PyJWT validates RSA signature before anything else. This is a fail in RSA signature validation step.
Did you try creating public key using the modulus n and exponent e values rather than using the x5c web signature.?
I did a implementation based on this blog post that uses modules and exponent that you can find here: gist.github.com/tmpapageorgiou/0c1188283f5ee450b517ffe2f8912c98
@Thiago did you figure this out? I am experiencing the same issue as well.
@BrandonSmith Unfortunately I have not yet. I'm still looking into it, though. I will definitely post here if I figure it out.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.