9

Problem

When I receive a JWK from Azure AD in Python, I would like to validate and decode it. I, however, keep getting the error "Signature verification failed".

My Setup

I have the following setup:

  1. Azure Setup
    In Azure I have created an app registration with the setting "Personal Microsoft accounts only".
  2. Python Setup
    In Python I use the MSAL package for receiving tokens. And I use a public key from Azure to verify the token.

Code

Using the credentials from the Azure Portal I set up a client for getting tokens.

import msal
ad_auth_client = msal.ConfidentialClientApplication(
    client_id = client_id,
    client_credential = client_secret,
    authority = "https://login.microsoftonline.com/consumers"
)
my_token = ad_auth_client.acquire_token_for_client(scopes=['https://graph.microsoft.com/.default'])

If I throw the token into a site like https://jwt.io/ everything looks good. Next I need public keys from Azure for verifying the token.

import requests
response = requests.get("https://login.microsoftonline.com/common/discovery/keys")
keys = response.json()['keys']

To match up the public key to the token, I use the 'kid' in the token header. I also get which algorithm was used for encryption.

import jwt
token_headers = jwt.get_unverified_header(my_token['access_token'])
token_alg = token_headers['alg']
token_kid = token_headers['kid']
public_key = None
for key in keys:
    if key['kid'] == token_kid:
        public_key = key

Now I have the correct public key from Azure to verify my token, but the problem is that it is a JWT key. Before I can use it for decoding, I need to convert it to a RSA PEM key.

from cryptography.hazmat.primitives import serialization
rsa_pem_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(public_key))
rsa_pem_key_bytes = rsa_pem_key.public_bytes(
    encoding=serialization.Encoding.PEM, 
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)
    

This is what the Azure Public Key looks like:

b'-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyr3v1uETrFfT17zvOiy0\n1w8nO+1t67cmiZLZxq2ISDdte9dw+IxCR7lPV2wezczIRgcWmYgFnsk2j6m10H4t\nKzcqZM0JJ/NigY29pFimxlL7/qXMB1PorFJdlAKvp5SgjSTwLrXjkr1AqWwbpzG2\nyZUNN3GE8GvmTeo4yweQbNCd+yO/Zpozx0J34wHBEMuaw+ZfCUk7mdKKsg+EcE4Z\nv0Xgl9wP2MpKPx0V8gLazxe6UQ9ShzNuruSOncpLYJN/oQ4aKf5ptOp1rsfDY2IK\n9frtmRTKOdQ+MEmSdjGL/88IQcvCs7jqVz53XKoXRlXB8tMIGOcg+ICer6yxe2it\nIQIDAQAB\n-----END PUBLIC KEY-----\n'

The last thing I need to do is to verify the token using the public key.

decoded_token = jwt.decode(
    my_token['access_token'], 
    key=rsa_pem_key_bytes,
    verify=True,
    algorithms=[token_alg],
    audience=[client_id],
    issuer="https://login.microsoftonline.com/consumers"
)

The result I get is:

jwt.exceptions.InvalidSignatureError: Signature verification failed

What I also tried

I also tried to follow this popular guide: How to verify JWT id_token produced by MS Azure AD? Placing the x5c into the certificate pre- and postfixes just generated errors of invalid formatting.

What is next?

Can you guys see any obvious errors? My main guess is that there is something wrong with the audience or the issuer, but I cannot pin down what it is, and Microsoft's documentation is horrible as always. Also, there is a secret key in the app registration in Azure, but it does not seem to work either.

Update

So it turns out that my verification code was correct, but that I was trying to verify the wrong token. After creating slight modifications I now receive an id_token, which can be decoded and verified.

Esben Eickhardt
  • 3,183
  • 2
  • 35
  • 56
  • *guess is that there is something wrong with the audience or the issuer* - wrong audience would cause ` jwt.InvalidAudienceError`. But the audience parameter is optional, so remove it for testing and see what happens. – jps Feb 02 '22 at 10:01
  • Do you know if issuer is also optional? If I only put in key, secret and algorithms I get:[_OpenSSLErrorWithText(code=151584876, lib=9, reason=108, reason_text=b'error:0909006C:PEM routines:get_name:no start line')] – Esben Eickhardt Feb 02 '22 at 10:02
  • it's PyJWT, right?! Then yes, issuer is also optional. https://pyjwt.readthedocs.io/en/stable/usage.html#issuer-claim-iss. The error seems t o be related to the key. Try like shown [here](https://stackoverflow.com/a/64598545/7329832) – jps Feb 02 '22 at 10:08
  • Yeah, it is PyJWT – Esben Eickhardt Feb 02 '22 at 10:29
  • 4
    hi @EsbenEickhardt can you show your "slight modifications" for id_token, please. – Elliot13 Jun 14 '22 at 15:53

3 Answers3

1

You acquired the token for the scope https://graph.microsoft.com/.default, which means the access token is for MS Graph API. You should not be validating that, it is the job of the API it is meant for. Graph API is also special and uses a different method of signing than other APIs.

juunas
  • 54,244
  • 13
  • 113
  • 149
  • 1
    So the token that I receive should not be validated? I would like to use Azure AD to access control, such that only users belonging to a certain AD Group have access to my app. The MSAL documentation only have examples for delegated access to the Graph API. So my though was that if only users belonging to a certain AD Group can get the tokens, and that I can validate the tokens, then I could grad access. Do you have another suggestion? :) – Esben Eickhardt Feb 02 '22 at 10:29
  • 1
    Yes, I can give some pointers :) Could you tell me what kind of application is this? Is this a Web application for example? Typically an application will: authenticate the user, validate the id token/access token for the app, (optional) check user roles in token, (optional) check app permissions in token, (optional) check user groups via token or MS Graph API. – juunas Feb 02 '22 at 10:36
  • Cool! So this is ment for very simple flask web apps written in Python. I have previously used jwt authentication where each endpoint has been decorated with a function validating the tokens. Now the organisation I work for wants us to used AD-authentication for the same, so my though was to use the same approach. However, I am having problems validating the tokens from azures. I prefer not to use sessions as in Azure's guide, as that is no longer best practice. Also, in the Microsoft documentation they don't have examples of how to validate their tokens. – Esben Eickhardt Feb 02 '22 at 12:58
  • 1
    Sounds like your app is a back-end Web app with no single page app (React, Angular etc.) front-end. These apps typically use the authorization code OAuth flow to get tokens. I've sadly not worked with Python so I can't really say what libraries to use for that.. – juunas Feb 02 '22 at 13:54
  • Thanks for your input, I will figure something out :) – Esben Eickhardt Feb 02 '22 at 15:02
0

Try changing your

scopes=['https://graph.microsoft.com/.default']

To

scopes:['[YOUR_CLIENT_ID]/.default']
0

The issue looks to be related to that the JWT you generated was for MS Graph API, which the results JWT would contain a "nonce" in JWT Header, and is not meant to be validated.

The tweak is to generate JWT with scope != https://graph.microsoft.com/.default, but instead in form = "scope": [ "api://client-id/.default" ].

After which you can verify JWT header no longer has "nonce" in JWT Header, and normal validation like code above would work.

ws_geek
  • 1
  • 1