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

Add file-cache option for AWS Lambda #141

Closed
wants to merge 38 commits into from

Conversation

vanhumbeecka
Copy link

@vanhumbeecka vanhumbeecka commented May 5, 2020

By submitting a PR to this repository, you agree to the terms within the Auth0 Code of Conduct. Please see the contributing guidelines for how to create and submit a high-quality PR for this repo.

Description

The caching option where cache = true does not work on AWS Lambda environments, resulting in too many JWKS endpoint calls.

This PR extends the options object of passportJwtSecret with an extra option called useTmpFileCache (optional boolean, default = false).
When enabled, the caching layer will use local file-storage for caching under /tmp folder.

This plays nicely with serverless like AWS Lambda (tested), but also GCP App Engine (not tested), which both uses the /tmp location for local file storage which is shared between multiple instances of the same service.

Tests have been added to the tests folder.

Testing

See the tests folder. mock-fs has been used for mocking the file-system.

Checklist

  • I have added documentation for new/changed functionality in this PR or in auth0.com/docs
  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used, if not master

@vanhumbeecka vanhumbeecka requested a review from a team May 5, 2020 13:38
@vanhumbeecka
Copy link
Author

Feedback on this PR is welcome.
I've added documentation in the Readme.md for clarification.

src/wrappers/cache.js Outdated Show resolved Hide resolved
@DaMo86
Copy link

DaMo86 commented May 28, 2020

would be a great improvement - waiting for the merge 👍

@oscarwest
Copy link

Looking forward to seeing this merged.

@iacoshoria
Copy link

Looking forward for this feature. 🚀

dependabot bot and others added 9 commits July 22, 2020 21:53
…h-4.17.19

Bump lodash from 4.17.15 to 4.17.19
Update Buffer initialization to non-deprecated method
* Fixes vulnerability in express-jwt

* Update tests using expressJwt
* Add async functions

* add tests

* extract custom promisify function

* Add method docs

* Update README

* Fix linter issues

Co-authored-by: David Patrick <[email protected]>
Use axios url parameter instead of baseURL
lbalmaceda and others added 8 commits August 18, 2020 14:54
These methods were added by auth0#161, but the PR did not include an update
to the matching Typescript type definitions. Without this update, these
new methods are unusable in Typescript projects.
Add missing async methods to Typescript type definitions
* style: no extra semi

* feat: no-extra-semi rule

Co-authored-by: David Patrick <[email protected]>
Co-authored-by: Luciano Balmaceda <[email protected]>
@davidpatrick
Copy link
Contributor

@vanhumbeecka thanks for the terrific contribution. Are you able to update this PR with the changes from #149 introduced by @oscarwest ? Or should we favor that PR? Thanks 🙏

@vanhumbeecka
Copy link
Author

Hi @davidpatrick, I'll update the PR based on feedback in this thread.
Should be able to do this in the next couple of days.

@vanhumbeecka
Copy link
Author

vanhumbeecka commented Sep 22, 2020

@davidpatrick Ok, so I changed this PR by changing the writeFile to writeFileSync as requested. Thereby this change also required me to add try-catch blocks instead of callbacks.
The tests remained unaffected. (I've not changed the tests according to #149 , since there, the done callback is incorrectly called outside of the callbacks where the checks happen with expect.)

Not sure why the codecov project is failing though...

@davidpatrick davidpatrick self-assigned this Sep 22, 2020
@davidpatrick
Copy link
Contributor

Brilliant thanks @vanhumbeecka 🙇 I will take a look

@davidpatrick
Copy link
Contributor

@vanhumbeecka thanks for having the patience on this PR. I recently had some more bandwidth to dive a little deeper into this library and discuss with the team some of the requests/issues surrounding this library.

It seems the crux of the issue is that in-memory caching doesn't work for environments with multiple nodes/servers.

We appreciate raising this issue, and the accompanying PR, however the proposed PR has a couple of issues:

  • No cache invalidation, Once it is written to /tmp/jwks-cache there is no invalidation of the cache allowing a rotated set to come in
  • No customization of tmpFileCache location
  • Too tightly coupled to this use-case -- No flexibility of cache implementation

A more flexible approach we think would be to allow the user to replace the memoizer(in-memory) cache with their own cache implementation. Similair to our sister library and its dummy-cache.

An example of what a custom cache might look like:

// This would belong to the client/consumer of this library
class CustomCache = {
  get(kid, getSigningKey) {
    const key = this.has(kid);
    if (key) { return Promise.resolve(key); ****}

    return new Promise((resolve, reject) => {
      getSigningKey(kid, (err, key) => {
        if (err) { return reject(err); }

        this.set(kid, key);
        return resolve(key);
      }
      );
    });
  },
  has(kid) {
    // if cache invalid (expired?) -- this.get
    // if cache valid -- retrieveFromCacheStore(kid)
  },
  set(kid, key) {
    // saveInCacheStore(kid, key)
  }
};

const customCache = new CustomCache();
const client = jwksClient({ cache: true, customCache })

Then inside of the cache wrapper we could implement it like so:

// ./src/wrappers/cache.js
export default function(client, { customCache, cacheMaxEntries = 5, cacheMaxAge = ms('10m') } = options) {
  const logger = debug('jwks');
  const getSigningKey = client.getSigningKey;

  if (customCache) {
    return customCache.get(kid, getSigningKey)
      .then(key => cb(null, key))
      .catch(err => cb(err));
  }

  return memoizer({
    //..//
  });

Let me know what you think and if you can see any instances where this wouldn't work for you. I can take ownership of the implementation, or if this is something that you want to take on I can assist.

@vanhumbeecka
Copy link
Author

Hi @davidpatrick, I can certainly agree with your assessment.
But I guess it's a choice between flexibility and somewhat ease-of-use. To respond to your statements:

  • No cache invalidation, Once it is written to /tmp/jwks-cache there is no invalidation of the cache allowing a rotated set to come in
  • Too tightly coupled to this use-case -- No flexibility of cache implementation

The current implementation is indeed specific to serverless environments. That also means that the /tmp location will automically be cleared from time to time (thereby invalidating it), although you don't really have control over the specific moments. (as is the case with AWS Lambda)

  • No customization of tmpFileCache location

True, but again, I went for 'easy-of-use' in this implementation, meaning adding a single flag to 'true' worked without worrying about configuration.

That being said, I guess most developers actually do want more flexibility and control, so I can't really argue with your statements in that regard.

So feel free to take over from here if you want to 👍 .
I'm already happy this issue gets attention to get fixed!

@davidpatrick
Copy link
Contributor

@vanhumbeecka perfect thanks for the quick response. I will be tackling this next week. Thanks again for raising the issue 🙇

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

Successfully merging this pull request may close these issues.

None yet