I am trying to verify azure active directory ID and access tokens using the described flow in the ms docs. I am doing this in the browser. I know it’s pointless to do it on the frontend. I still want to know how to do it. Just out of curiosity.
There is something similar on https://jwt.io/. It’s meant to debug tokens. It can tell if a signature is valid or not.
In my code, the verification always fails. I am unsure If I am using SubtleCrypto correctly in this context. I am also unsure if I am choosing the right parameter for its functions, like the algorithm.
The header of the access token looks always something like this, using RS256.
{
"typ": "JWT",
"nonce": "<redacted>",
"alg": "RS256",
"x5t": "<redacted>",
"kid": "<redacted>"
}
The matched JWK looks like this.
{
"kty": "RSA",
"use": "sig",
"kid": "<redacted>",
"x5t": "<redacted>",
"n": "<redacted>",
"e": "<redacted>",
"x5c": ["<redacted>"]
}
I have created this function to get the JWK using the issuer URL in the claims and kid property in the header, and eventually validate the signature. crypto.subtle.verify
returns always false.
async function verifyToken(rawToken) {
// parse the token into parts
const [encodedHeaders, encodedClaims, signature] = rawToken.split(".");
const header = JSON.parse(atob(encodedHeaders));
const claims = JSON.parse(atob(encodedClaims));
// get the openid config using the issuer url
const issuer_url = claims.iss.endsWith("/") ? claims.iss : claims.iss + "/";
const openIdConfiguration = await (
await fetch(`${issuer_url}.well-known/openid-configuration`)
).json();
// get the jwk list
const jwkList = await (
await fetch(openIdConfiguration.jwks_uri)
).json();
// find the jwk for the kid in the token header
const matchedKey = jwkList.keys.find(k => k.kid === header.kid)
// return early if no jwk is found
if (!matchedKey) return {
rawToken,
header,
claims,
signature,
openIdConfiguration,
jwkList,
};
// import the jwk into a a crypto key
const pubkey = await crypto.subtle.importKey(
"jwk",
matchedKey,
{ name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } },
true,
["verify"],
);
// verify the signature
const verified = await crypto.subtle.verify(
{ name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } },
pubkey,
new TextEncoder().encode(signature),
new TextEncoder().encode(encodedHeaders + "." + encodedClaims),
);
// return the results
return {
rawToken,
header,
claims,
signature,
openIdConfiguration,
jwkList,
matchedKey,
verified
};
};