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

Added caching to well-known configuration #48

Merged
merged 3 commits into from
Oct 20, 2016
Merged

Conversation

lboyette-okta
Copy link
Contributor

@lboyette-okta lboyette-okta commented Oct 15, 2016

@rchild-okta @magizh-okta

To get the authorization_endpoint, userinfo_endpoint, and jwks_uri, we have to call .well-known/openid-configuration. This information rarely changes (if ever), and we use the information often, so to prevent unnecessary api calls, we cache the result.

@magizh-okta
Copy link
Contributor

Description in the commit ? Would be helpful for other devs to understand why this PR is needed.

var wellKnown = require('../xhr/well-known');

describe('getWellKnown', function() {
util.itMakesCorrectRequestResponse({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it might be better to just test that "we get well known, and use the cached version when it's stored", i.e. the end-to-end where we can verify that we got the same response both times, but only made a request once

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, it's easy enough to add an extra getWellKnown request

var wellKnown = require('../xhr/well-known');

describe('getWellKnown', function() {
util.itMakesCorrectRequestResponse({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also seems like we probably need a test for not using the cached item if it exceeds the time when it expired (1 day)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, will add

@@ -4,15 +4,26 @@ var cookies = require('./cookies');
var Q = require('q');
var AuthApiError = require('./errors/AuthApiError');
var config = require('./config');
var storageBuilder = require('./storageBuilder');

var httpCache = storageBuilder(localStorage, config.CACHE_STORAGE_NAME);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something that we might want to consider for the future, but it seems like overkill to have a separate instance of storage for every storage key (as opposed to having 1 localStorage, and 1 cookieStorage for example that each know how to lookup the val given a key).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough

if (options.responseCacheDuration) {
var cacheContents = httpCache.getStorage();
var cachedResponse = cacheContents[url];
if (cachedResponse && Date.now()/1000 < cachedResponse.expiresAt) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we simplify and remove the math (i.e. just use Date.now() and work in ms) since we control both ends and don't plan on exposing this outwards?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had it that way originally, but thought this way was easier to read (and easier to understand when debugging the cache)

if (options.responseCacheDuration) {
var cacheContents = httpCache.getStorage();
var cachedResponse = cacheContents[url];
if (cachedResponse && Date.now()/1000 < cachedResponse.expiresAt) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also curious how we're planning on handling the keys call, specifically:

  1. First time user, gets keys, it's cached
  2. Second time coming in, cached and kid is still there (no problem)
  3. Keys rotate
  4. Make request, is cached, kid is not there; make request again <-- what happens if for whatever reason the kid is not there?

I.e. it doesn't look like we expose whether we retrieved the response from the cache or not - is that going to be a problem when we need to have logic like "see what we have, looks like it's not there, make a request to get the new info".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code from the next PR

function getKey(sdk, issuer, kid) {
  return getWellKnown(sdk, issuer)
  .then(function(wellKnown) {
    // Generate a jwks uri
    var jwksUri = wellKnown['jwks_uri'];

    // Check our kid against the cached version (if it exists)
    var cacheContents = httpCache.getStorage();
    var cachedResponse = cacheContents[jwksUri];

    var cachedKey = util.find(cachedResponse.keys, {
      kid: kid
    });

    if (cachedKey) {
      return cachedKey;
    }

    // Pull the latest keys if the key wasn't in the cache
    return http.get(jwksUri, {
      responseCacheDuration: config.DEFAULT_CACHE_DURATION
    })
    .then(function(keys) {
      var key = util.find(keys, {
        kid: kid
      });

      if (key) {
        return key;
      }

      throw new AuthSdkError('The key id, ' + kid + ', was not found in the server\'s keys');
    });
  });
}

accessToken = options.accessToken;

if (options.responseCacheDuration) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need to pass in responseCacheDuration, or can we just get it from the config? I.e. are we planning on exposing this to the user later on down the line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could change depending on the response you're trying to cache (in theory we could store the keys indefinitely and only request new ones when key validation fails). I guess I'm defaulting to 2 weeks for everything now, so I'll just change that here.

@@ -47,6 +58,15 @@ function httpRequest(sdk, options) {
cookies.setCookie(config.STATE_TOKEN_COOKIE_NAME, res.stateToken, res.expiresAt);
}

if (res && options.responseCacheDuration) {
var cache = httpCache.getStorage();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to provide a method for "updateStorage" - namely, underneath the hood it's going to get, write, and set, but we can just set a new url with the value here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense to me

url: url,
method: 'POST',
args: args,
dontSaveResponse: dontSaveResponse
});
cacheState: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, why is cacheState true for post? When would we actually cache a post response?

@@ -45,7 +46,9 @@ function loadPopup(src, options) {

function getWellKnown(sdk) {
// TODO: Use the issuer when known (usually from the id_token)
return http.get(sdk, sdk.options.url + '/.well-known/openid-configuration');
return http.get(sdk, sdk.options.url + '/.well-known/openid-configuration', {
responseCacheDuration: config.DEFAULT_CACHE_DURATION
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to other comment, but seems like it'd be nicer to make the a boolean "cacheResponse" and just load the time from the config. Or do you foresee having different cache timeouts for different requests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with changing everything to use DEFAULT_CACHE_DURATION for now.

url: url,
method: 'POST',
args: args,
dontSaveResponse: dontSaveResponse
});
cacheState: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I think I see what's going on here - the "dontSaveResponse" from before was the idea of remembering the previous response in the authn flow.

I think "cacheState" is a confusing variable name now that we have a cache that persists past the current auth flow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cacheAuthnState?

@@ -13,10 +13,10 @@ function httpRequest(sdk, options) {
var url = options.url,
method = options.method,
args = options.args,
cacheState = options.cacheState,
cacheAuthnState = options.cacheAuthnState,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit pedantic, but how about "saveAuthnState"? The difference for me is that we're using "cacheResponse" to mean "let's save this response for up to the DEFAULT_CACHE_DURATION, and I do not expect the response to change very frequently", where authState gets updated on every request and is expected to hold different values as the user goes through the auth flow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I buy that, will change

Copy link
Contributor

@rchild-okta rchild-okta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other changes look good though, so update variable name and then 🚀

@lboyette-okta lboyette-okta merged commit af21c3b into master Oct 20, 2016
@jmelberg-okta jmelberg-okta deleted the lb-100724-caching branch April 23, 2018 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants