Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key loaded via interceptor causing a type error #293

Closed
ghost opened this issue Apr 4, 2022 · 2 comments
Closed

Key loaded via interceptor causing a type error #293

ghost opened this issue Apr 4, 2022 · 2 comments
Labels

Comments

@ghost
Copy link

ghost commented Apr 4, 2022

Describe the problem

Returning keys from the function passed in for getKeysInterceptor is causing a type error.

2022-04-04T17:52:04.682Z	dea71b6b-1398-4763-bf96-27c05e628c64	INFO	Error while verifying the token TypeError: jwks must be a JSON Web Key Set formatted object
    at Object.asKeyStore (/var/task/node_modules/jose/lib/jwks/keystore.js:166:11)
    at retrieveSigningKeys (/var/task/node_modules/jwks-rsa/src/utils.js:4:30)
    at JwksClient.<anonymous> (/var/task/node_modules/jwks-rsa/src/wrappers/interceptor.js:15:21)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async /var/task/node_modules/jwks-rsa/src/wrappers/rateLimit.js:24:23

The keys in question are fetched via getSigningKey, direct from auth0.

What was the expected behavior?

Loaded keys should work without an error.

Reproduction

Assuming the following code (in a Lambda authorizer):

export function makeGetSigningKey(credentials: Auth0Secrets) {
    const client = jwksClient({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: `${credentials.AUTH0_DOMAIN}/.well-known/jwks.json`,
        getKeysInterceptor: async () => loadKeys(),
    });

    const getSigningKey: (kid: string) => Promise<jwksClient.SigningKey> =
        util.promisify(client.getSigningKey);
    getSigningKey.bind(jwksClient);
    return async (kid: string) => {
        const k = await getSigningKey(kid);
        cacheKey(kid, k);
        return k;
    };
}

const KEY_FILE_CACHE = path.join(path.sep, "tmp", "key");

export function cacheKey(
    kid: string,
    k: jwksClient.SigningKey,
    cacheDir: string = KEY_FILE_CACHE
) {
    try {
        if (!fs.existsSync(cacheDir)) {
            fs.mkdirSync(cacheDir);
        }
        const keyFile = path.join(cacheDir, kid);
        if (!fs.existsSync(keyFile)) {
            fs.writeFileSync(keyFile, JSON.stringify(k));
        }
    } catch (e) {
        console.debug(`Problem caching keys, ${e}`);
    }
}

export async function loadKeys(
    cacheDir: string = KEY_FILE_CACHE
): Promise<JSONWebKey[]> {
    try {
        if (fs.existsSync(cacheDir)) {
            return fs.readdirSync(cacheDir).map((f) => {
                const keyFile = path.join(cacheDir, f);
                const b = fs.readFileSync(keyFile);
                return JSON.parse(b.toString());
            });
        }
    } catch (e) {
        console.debug(`Problem reading keys from disk cache, ${e}`);
    }
    return [];
}
  • Make a call to get a signing key
  • Keep the client loaded or disable cache so the client tries to use the interceptor
  • Make another call to get the same signing key

Environment

  • Version of this library used: v2.0.4
  • Which framework are you using, if applicable: AWS Lambda, in particular this is used in a token authorizer
  • Other modules/plugins/libraries that might be involved:
  • Any other relevant information you think would be useful: I suspect this is because the type used in [SDK-2626] getKeysInterceptor types #251 isn't complete enough. The code that actually throws an error is expecting a SigningKey but JSONWebKey lacks the method, getPublicKey so trips the error
@adamjmcgrath
Copy link
Contributor

Hi @thomas-yaa - thanks for raising this

The return value of getSigningKey is not a JSON Web Key Set, so what you're caching is a SigningKey not a JWKS.

getKeysInterceptor expects you to return a JWKS, so when you restore the cache and return a SigningKey, you will get "Error while verifying the token TypeError: jwks must be a JSON Web Key Set formatted object"

Your code probably works the first time when the cache is empty, then fails when the cache is being restored.

If you want to cache the JWKS in a file system you'll need to fetch it yourself and save it in the interceptor, eg

async function loadKeys(jwksUri, cacheDir = KEY_FILE_CACHE) {
  try {
    if (fs.existsSync(cacheDir)) {
      // load JWKS from cache
    }
  } catch (e) {
    console.debug(`Problem reading keys from disk cache, ${e}`);
  }
  const jwks = await request(jwksUri);
  cacheKeys(jwks);
  return jwks;
}

Also you don't need this code:

const getSigningKey: (kid: string) => Promise<jwksClient.SigningKey> =
        util.promisify(client.getSigningKey);
    getSigningKey.bind(jwksClient);

As client.getSigningKey already returns a promise.

@adamjmcgrath
Copy link
Contributor

Closing due to inactivity, ping me if you want to reopen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant