Open henrikericsson opened 7 months ago
this is the error jose is throwing that jwks isn't logging... at least in my case
TypeError: CryptoKey is not extractable
at /opt/core/api/node_modules/jwks-rsa/node_modules/jose/dist/browser/runtime/asn1.js:12:15
at genericExport (/opt/core/api/node_modules/jwks-rsa/node_modules/jose/dist/browser/runtime/asn1.js:7:30)
at exportSPKI (/opt/core/api/node_modules/jwks-rsa/node_modules/jose/dist/browser/key/export.js:4:34)
at /opt/core/api/node_modules/jwks-rsa/src/utils.js:53:30```
It's using the browser runtime, not node. Not sure if that's whats supposed to be happening? Or maybe theres a bun one. Anyway, when using browser the ext
field is looked at in the jwt. This gets past the issue for me. I think.
diff --git a/node_modules/jwks-rsa/src/utils.js b/node_modules/jwks-rsa/src/utils.js
index dcaf146..e40e613 100644
--- a/node_modules/jwks-rsa/src/utils.js
+++ b/node_modules/jwks-rsa/src/utils.js
@@ -43,6 +43,7 @@ async function retrieveSigningKeys(jwks) {
for (const jwk of jwks) {
try {
+ jwk.ext = true
const key = await jose.importJWK(jwk, resolveAlg(jwk));
if (key.type !== 'public') {
continue;
Thank you for your responses and for looking into this issue. I appreciate the time and effort you’ve put into investigating this.
I agree that the error seems to be related to the crypto library, specifically with the CryptoKey not being extractable. However, I would like to point out that the same code works perfectly fine when running in a Node.js environment. This suggests that the issue might not be with the jwks-rsa library itself, but rather with how it’s being used or configured in the Bun environment.
The jwks-rsa library is as far as I know designed for Node.js, and it seems to be functioning as expected in that context. Modifying the library directly might not be the best approach, as it could potentially introduce other issues or side effects. Instead, I believe we should focus on understanding why the library is not working as expected in the Bun environment.
It’s possible that there might be some configuration changes needed in Bun, or perhaps some adjustments within the framework itself. I think it would be beneficial to explore these possibilities further.
No worries, I’m hitting the same issue.
I’m not suggesting the change is an actual fix, just working out where the problem is :)
Looks like it is supposed to use the browser runtime: https://github.com/panva/jose/blob/main/package.json#L80
Version 2.1.5 works for me, version 3.1.0 not.
A few months ago, I experimented with the 2.x version of the jwks-rsa library, using an older version of Bun but encountered the same issue then as I am with the latest version of Bun and the 3.x version of jwks-rsa.
But I haven't tried using the latest version of bun with an older version of jwks-rsa.
It is better to use the Jose library since it is certified for the use with Bun.
Okay I want to bring attention to this again. I'm having similar issues with Bun v1.1.22. I spending hours tweaking my code in hopes it was application related, I came across this issue and ported my code to node and it verified the jwt just fine. Do you need another repro snippet or is this issue enough? @Jarred-Sumner
This is what I'm running and azureActiveDirectory
fails in bun but succeeds in node. The failure message is
JsonWebTokenError {
stack: "Error\n at <anonymous> (.../node_modules/jsonwebtoken/verify.js:103:19)\n at <anonymous> (.../src/jwt.ts:27:7)\n at processTicksAndRejections (native)",
name: "JsonWebTokenError",
message: "error in secret or public key callback: The JWKS endpoint did not contain any signing keys",
toString: [Function: toString],
}
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import { AZURE_AD_TENANT_ID } from './secrets';
const JWKS_URI = `https://login.microsoftonline.com/${AZURE_AD_TENANT_ID}/discovery/v2.0/keys`;
const UNAUTHORIZED = 'Unauthorized';
// Create a JWKS client to retrieve JWKS URI signing keys.
const client = jwksClient({ jwksUri: JWKS_URI });
function getKey(header: any, callback: jwt.SigningKeyCallback) {
if (typeof callback !== 'function') {
console.log('Callback is not a function');
return;
}
if (!header) {
callback(new Error('Header is not provided'));
return;
}
// client.getKeys().then(r => console.log(r))
client.getSigningKey(header.kid, (err, key) => {
if (err || !key) {
console.log("ERROR HERE")
callback(err);
return;
}
if (!key) return
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
}
export const azureActiveDirectory = (token: string) => {
// Check if the token is present.
if (!token) {
return Promise.reject(`${UNAUTHORIZED}: No token provided`)
}
return new Promise((resolve,reject) => {
jwt.verify(
token,
getKey,
{
algorithms: ['RS256']
},
(err, decode) => {
if (err) {
console.log('Error in token verification:', err);
reject(UNAUTHORIZED)
return;
}
resolve(decode)
}
);
})
};
`
I am using a Bun backed server that works in Node but not in Bun. This code has different code paths for the runtimes:
for (const jwk of jwks) {
try {
jwk.ext = true
const key = await jose.importJWK(jwk, resolveAlg(jwk));
console.log('jwks-rsa key.type:', key.type);
if (key.type !== 'public') {
continue;
}
let getSpki;
console.log('jwks-rsa key[Symbol.toStringTag]:', key[Symbol.toStringTag]);
switch (key[Symbol.toStringTag]) {
case 'CryptoKey': {
// 👉 BUN WILL GO THIS CODE PATH
const spki = await jose.exportSPKI(key);
getSpki = () => spki;
break;
}
case 'KeyObject':
// Assume legacy Node.js version without the Symbol.toStringTag backported
// Fall through
default: {
// 👉 NODE WILL GO THIS CODE PATH
getSpki = () => key.export({ format: 'pem', type: 'spki' });
}
}
console.log('jwks-rsa results.push...', {
get publicKey() { return getSpki(); },
get rsaPublicKey() { return getSpki(); },
getPublicKey() { return getSpki(); },
...(typeof jwk.kid === 'string' && jwk.kid ? { kid: jwk.kid } : undefined),
...(typeof jwk.alg === 'string' && jwk.alg ? { alg: jwk.alg } : undefined)
});
results.push({
get publicKey() { return getSpki(); },
get rsaPublicKey() { return getSpki(); },
getPublicKey() { return getSpki(); },
...(typeof jwk.kid === 'string' && jwk.kid ? { kid: jwk.kid } : undefined),
...(typeof jwk.alg === 'string' && jwk.alg ? { alg: jwk.alg } : undefined)
});
} catch (err) {
console.error('jwks-rsa error!');
console.error(err)
continue;
}
}
Debug logs show that for Node:
jwks-rsa key[Symbol.toStringTag]: KeyObject
jwks-rsa fallthrough
jwks-rsa results.push... {
publicKey: [Getter],
rsaPublicKey: [Getter],
getPublicKey: [Function: getPublicKey],
kid: '***'
}
And for Bun:
jwks-rsa key[Symbol.toStringTag]: CryptoKey
jwks-rsa error!
7 | const genericExport = async (keyType, keyFormat, key) => {
8 | if (!isCryptoKey(key)) {
9 | throw new TypeError(invalidKeyInput(key, ...types));
10 | }
11 | if (!key.extractable) {
12 | throw new TypeError('CryptoKey is not extractable');
It's using the browser runtime, not node. Not sure if that's whats supposed to be happening? Or maybe theres a bun one. Anyway, when using browser the
ext
field is looked at in the jwt. This gets past the issue for me. I think.
This jwk.ext = true
"fix" works in Bun backend too:
jwks-rsa key[Symbol.toStringTag]: CryptoKey
jwks-rsa results.push... {
publicKey: [Getter],
rsaPublicKey: [Getter],
getPublicKey: [Function: getPublicKey],
kid: "***",
}
What exactly does ext = true
do that makes this work? Is it insecure to use this "fix", or can it be accepted? 🤔
What version of Bun is running?
1.1.4+fbe2fe0c3
What platform is your computer?
Linux 5.15.146.1-microsoft-standard-WSL2 x86_64 x86_64
What steps can reproduce the bug?
Create an express middleware and apply it to any route served via express using https module.
Middleware:
What is the expected behavior?
A collection of signing keys should be returned from the jwksUri using the jwks-rsa package.
What do you see instead?
No keys are returned and fails with error:
Additional information
The same flow works just fine running it with node
Middleware: