pythonjwtazure-active-directorysignaturejose

How to validate a JWT from AzureAD in python?


I have set up an authorization server using AzureAD. For testing it I am currently applying an implicit flow and to get a token and id_token I use https://oidcdebugger.com/.

Now I am trying to figure out how to properly validate a the token on the resource server side. I always get a jose.exceptions.JWTError: Signature verification failed. Can you help me find out what I am doing wrong? I am really new to JWT validation and probably there is some obvious error somwhere.

As defined by the OIDC metadata endpoint at https://login.microsoftonline.com/{tenant_id}/v2.0/.well-known/openid-configuration keys for signature validation can be found at https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys. The key ids on that endpoint match the kid value in my token headers. So, I am positive that those are my keys. They look like this:

{
   "kty": "RSA",
   "use": "sig",
   "kid": "<the-key-id>",
   "x5t": "<the-key-id>",
   "n": "<a-long-hash>",
   "e": "AQAB",
   "x5c": ["<a-long-hash-I-guess-thats-the-public-key"],
   "issuer": "https://login.microsoftonline.com/<my-tenant-id>/v2.0"
}

In the answer of this post the key is constructed by hand using cryptography.x509. I tried the same but I had to change a few details to make it run. I am .encodeing the string and I have to pass an iterable to the decode function.

import requests
from jose import jwt
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend

AZURE_TENANT_ID = '<my-tenant-id>'
AZURE_KEYS = requests.get(url='<my-jwks_url>').json()['keys']

PEMSTART = "-----BEGIN CERTIFICATE-----\n"
PEMEND = "\n-----END CERTIFICATE-----\n"


def decode(token: str, keys: list):
    token_header = jwt.get_unverified_header(token=token)
    x5t = token_header['x5t']

    key = [d for d in keys if d['x5t'] == x5t][0]
    mspubkey = key['x5c'][0]

    cert_str = PEMSTART + mspubkey + PEMEND
    cert_obj = load_pem_x509_certificate(cert_str.encode(), default_backend())
    public_key = cert_obj.public_key()

    return jwt.decode(
        token=token,
        subject='<my-subject>',
        audience='<my-audience>',
        issuer=f'https://sts.windows.net/{AZURE_TENANT_ID}/',
        algorithms=['RS256'],
        key=[public_key])

Solution

  • It seems like I am not supposed to validate the (access) token, only the id_token signature. The validation with jose also works by just supplying the key dict as keys argument (no need to construct certificate).

    Apparently the (access) token from AzureAD is not necessarily a standard JWT. It's just supposed to be used for accessing the MS GraphAPI: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/609#issuecomment-529537264