Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to verify JWT id_token produced by MS Azure AD?

I have an angularjs SPA web app which uses ADAL-JS (and adal-angular). It's set up to authenticate vs our corporate AD in MS Azure. The log-in flow seems to work correctly, and the SPA receives an id_token.

Next, when the user clicks a button, the SPA makes a request to a REST API I am hosting on AWS API Gateway. I am passing the id_token on the Authorization: Bearer <id_token> header. The API Gateway receives the header as intended, and now has to determine if the given token is good or not to either allow or deny access.

I have a sample token, and it parses correctly on https://jwt.io/ but I have so far failed to find the Public Key or Certificate I should use to verify the signature. I have looked in:

  • https://login.microsoftonline.com/{tenantid}/federationmetadata/2007-06/federationmetadata.xml
  • https://login.microsoftonline.com/{tenantId}/discovery/keys
  • https://login.microsoftonline.com/common/.well-known/openid-configuration (to get the jwks_uri)
  • https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
  • https://login.microsoftonline.com/common/discovery/keys
  • https://login.microsoftonline.com/common/discovery/v2.0/keys

I think I should use the value of the x5c property of the key in https://login.microsoftonline.com/common/discovery/keys matching the kid and x5t properties from the JWT id_token (currently a3QN0BZS7s4nN-BdrjbF0Y_LdMM, which leads to an x5c value starting with "MIIDBTCCAe2gAwIBAgIQY..." ). However, the https://jwt.io/ page reports "Invalid Signature" (I also tried wrapping the key value with "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----").

Also, is there a (possibly python) library that can facilitate the verification of a given id_token as in the case above (so that I won't have to go grab the signing key on the fly myself?)... The best I could find (ADAL for python) doesn't seem to provide this feature?

like image 767
FOR Avatar asked Mar 31 '17 14:03

FOR


2 Answers

The best solution I could put together so far:

Grab the certificate (the first value in the x5c property array) from either https://login.microsoftonline.com/common/discovery/keys or https://login.microsoftonline.com/common/discovery/v2.0/keys, matching kid and x5t from the id_token.

Wrap the certificate in -----BEGIN CERTIFICATE-----\n and \n-----END CERTIFICATE----- (the newlines seem to matter), and use the result as Public Key (in conjunction with the id_token, on https://jwt.io/ ).

Of course, your actual use case will likely be to have some program validate the incoming JWT id_tokens, so your goal won't be to simply get the token to validate through the web UI on https://jwt.io/.

For instance, in python, I need something like this:

#!/usr/bin/env python

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

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

mspubkey = "The value from the x5c property"
IDTOKEN = "the id_token to be validated"
tenant_id = "your tenant id"

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

decoded = jwt.decode(IDTOKEN, public_key, algorithms=['RS256'], audience=tenant_id)
if decoded:
    print "Decoded!"
else:
    print "Could not decode token."

For a list of JWT libraries in various languages, see the JWT Site. I'm using pyjwt, and its cryptography dependency (which has binary dependencies, so needs to be built and packaged for the target OS).

And then, of course, you can verify additional details such as the claims as recommended here.

like image 136
FOR Avatar answered Oct 18 '22 03:10

FOR


For a JVM solution, using com.nimbusds:numbus-jose-jwt:4.29 is the most straight forward way to parse and validate a signed RSA256 id_token. The following Scala code parses the JWT token with a JSON Web Key:

    val jwt = SignedJWT.parse(token)

    val n = new Base64URL("Your Modulus Component of RSA Key")
    val e = new Base64URL("AQAB")
    val rsaKey = new RSAKey.Builder(n, e).keyUse(KeyUse.SIGNATURE).algorithm(JWSAlgorithm.RS256).build()

    val verified = jwt.verify(new RSASSAVerifier(rsaKey))

Your application would still need to fetch the JSON Web Key Set from Azure Active Directory B2C discovery/v2.0/key dynamically to get the set of keys potentially used by AAD B2C. This should probably be cached and have a TTL of not more than 24 hours for efficiency.

like image 28
k.c. sham Avatar answered Oct 18 '22 05:10

k.c. sham