I trying to decode a JWT `access_token` produced b...
# cfml-general
r
I trying to decode a JWT
access_token
produced by Azure AD returned to me from a Nuxt SPA. I can drop the
access_token
into jwt.io and it will decode but it says it has an "Invalid signature". When I offer it to the ColdBox module cbSecurity
decode()
method when the algorithm is set to
RS256
it says "Cannot decode token: Invalid PEM key". The Azure AD consultant is saying.
Copy code
so looks like the API may have to do a little leg work itself. In .net, you pass in the well-known url and the auth middleware knows what to do. Here it looks like they need to be provided the pub cert to validate the signature and plumb that into their CF app. As a starting point, they can get the cert used for that token from <https://login.microsoftonline.com/*******************/discovery/v2.0/keys>, and finding the appropriate kid (married up with the kid in the token)

So tactical 'let just get it working' would be to provide that cert to the CF app. Ideal/most robust would be the CF API accepting a config param on startup specifying the well-known endpoint (<https://login.microsoftonline.com/*******************/v2.0/.well-known/openid-configuration>) - that then references the jwks url, and read the kid in the passed token to lookup the appropriate cert on the jwks url. Means if anything ever changes, the CF app should gracefully handle it all
I've looked at the content behind those two URL and they look like JSON packets rather than certificates which is what I thought I needed to provide to be able to check the signature on the
access_token
As you may have guessed, this is outside of my comfort zone. Can someone help me translate this before I go into a meeting tomorrow about it?
b
Try doing a cfdump each step of the way. That might give you missing errors or details. Is it possible things aren't set up right with Azure?
r
I'm really in the dark about the Azure and Nuxt steps as they are being done by others. I've just been given the
access_token
in an Authorization header and been asked to decode it. I'm guess the token is good as jwt.io can decode it but with the warning that the signature is not valid. Which I guess is fair as they shouldn't know what the key is. I can try and see what the cbSecurity.decode() method is doing and falling over with but that's the extent of the CF code-side of things. I was expecting to be given a PEM certificate that I could use to check the signature with but those JSON packets don't look like certs.
b
Are both the service provider (SP) and identity provider (IP) set up? I'm assuming Azure is the IP in your case and then CF would be the SP. But they have to be configured in the CF admin properly and match what is in Azure.
r
Hummm, how would I configure CF, Lucee in this case, as the SP? I appreciate your thoughts Brian but I'm still not sure this is the issue. We're just using Nuxt and Azure AD to authenticate the user credentials. Once authenticated it'll just be the front-end Nuxt calling my Lucee API with the
access_token
in the Authorization header. We won't be going to AAD again. The app is all front-end Nuxt and backend CFML API. Nothing on the Azure side apart from this authentication via AD. Or am I missing something fundamental here?
j
@richard.herbert If you are looking to parse and verify a JWT using the provided Microsoft keys here are a couple thoughts that might help. Those keys provided by Microsoft (at https://login.microsoftonline.com/*******************/discovery/v2.0/keys) are in JWK format, the endpoint is returning a JSON object with a key "keys" which is an array of keys in JWK format. (see https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets) I am not sure how/if cbsecurity works with JWTs that it doesn't create itself, but you should be able to use the underlying jwt-cfml library to handle this use case. Basically you would need to read in the key array from the above MS endpoint and store those keys somewhere (I would expect that Microsoft is rotating those keys over time, so you might need logic to refresh this list periodically). Each of those keys has a "kid" (key id) property - and a "kid" will be specified in the header of the JWT you are trying to parse. So if you use the jwt-cfml library you would take the JWT you received and call
.parseHeader(jwt)
(see https://github.com/jcberquist/jwt-cfml#getting-the-token-header). You would take the "kid" from the parsed header and find the key with that "kid" in the keys MS provides. Then you would call
.decode(jwt, jwk, 'RS256')
to decode and verify the JWT. (see https://github.com/jcberquist/jwt-cfml#decoding-tokens) Hope this helps!
p
Base 64 decode the token
r
@paul
Base 64 decode the token
I didn't realise that but useful knowledge. I can now decode the claim out of the
access_token
with that but I'd still like to validate the signature. @jcberquist There's a lot in your reply, which I appreciate, but some of the terminology is unfamiliar to me. This what I've done and where I'm now stuck. By base64 decoding the
access_token
header I can get algorithm and token type which give me the
kid
value. From
<https://login.microsoftonline.com/*******************/v2.0/.well-known/openid-configuration>
I can read the
jwks_uri
value that looks like
<https://login.microsoftonline.com/*******************/discovery/v2.0/keys>
and from there I can find the key where the
kid
values match. With that I can get the
x5c
value for that key. I think I have all the parts but this is where I get stuck. When I do
decode( access_token, x5c, 'RS256')
I get "_Invalid PEM key_". I'm obviously doing something wrong or missed a step but I'm at a loss to know what! Your help would be appreciated. I'm happy to share actual values with you by DM if you are willing to take a look. Also I'm at the point where I'm happy to pay for some of your time.
j
Did you try not the
x5c
value, but the entire JSON key object (converted to a CFML struct)? Specifically it should have the keys
n
and
e
(as an RSA public key).
r
Okay, I've tried passing in the key object, which looks like this (intentionally snipped).
And now I get "_Signature is Invalid_"
j
Did you parse the key struct into a JWK? I've something like this before with the id_token
cfhttp(method="get", url=LoginAuth.GetJwksUri(), result="JwksRequest")
if(JwksRequest.ResponseHeader.Status_Code != "200"){
// handle not being able to connect to the auth service
}
var Keys = JwksRequest.FileContent.DeserializeJson().keys
var JwtUtils = new <http://path.to|path.to>.jwt.library
var SigningKeyId = JwtUtils.GetHeader(form.id_token).kid
var SigningKey = Keys.filter(key => return key.kid == SigningKeyId)
var Jwk = JwtUtils.parseJwk(SigningKey.first())
try {
var IdTokenResult = JwtUtils.decode(
token = form.id_token,
key = Jwk,
algorithms = "RS256",
claims = {
aud : LoginAuth.GetClientId(),
tid : LoginAuth.GetTenantId(),
..... more claims as needed ........
}
)
} catch(any e){
// handle not being able to validate and decode
}
// lookup the user and log them in if you find a match
r
Thanks for your thoughts John. JC messaged me privately with this link which seems to infer that the
access_token
signature can't be verified as it is currently formatted. Which is reassuring that I was doing everything right but the token was never going to verify. I need to go back to the author of that token with this information. https://stackoverflow.com/questions/76001593/azure-jwt-verification-in-go-is-not-working