-
Notifications
You must be signed in to change notification settings - Fork 271
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
Conversation
de9baaa
to
f6cb272
Compare
f6cb272
to
fe5c376
Compare
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({ |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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:
- First time user, gets keys, it's cached
- Second time coming in, cached and kid is still there (no problem)
- Keys rotate
- 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".
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this 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 🚀
@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.