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

Pace token refresh requests to avoid rate-limiting #79

Merged
merged 1 commit into from
Feb 14, 2020

Conversation

mkistler
Copy link
Contributor

@mkistler mkistler commented Feb 1, 2020

This PR modifies the token refresh logic to pace refresh requests to avoid exceeding rate-limiting by the token server. This design also improves performance by continuing to use an existing token while the refresh is performed concurrently.

I also cleaned up some misinformation in the CONTRIBUTING.md that I uncovered in the process of developing this change.

@mkistler mkistler requested review from dpopp07 and ricellis February 1, 2020 16:57
Copy link
Member

@dpopp07 dpopp07 left a comment

Choose a reason for hiding this comment

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

The code looks good but I want to make sure I understand the implementation and I have one question.

This is how I understand it: If the code determines that it's time for a refresh, it kicks off a token request but in the meantime it just returns the token that's already been stored. If any more requests come in during the refresh, it still uses the stored token thanks to the 60 second addition to the time.

If that's correct, I have one question: what happens in the case that an app makes a request for which a token is retrieved, idles for long enough that the token expires completely, and then tries to make another request? It seems like the refresh would get kicked off but the stored token would still be used initially, resulting in a failed request. I may be misunderstanding but if that's the case, it might be a good idea to add a check that says "if the token is completely expired, request a new one in a blocking manner" like we do for the initial token grab. What are your thoughts?

CONTRIBUTING.md Show resolved Hide resolved
@@ -90,13 +90,19 @@ export class JwtTokenManager {
* has expired.
*/
public getToken(): Promise<any> {
if (!this.tokenInfo[this.tokenName] || this.isTokenExpired()) {
if (!this.tokenInfo[this.tokenName]) {
Copy link
Member

@ricellis ricellis Feb 4, 2020

Choose a reason for hiding this comment

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

I agree with @dpopp07 - I think the expiry check needs to remain here so that a synchronous request for a new token can be made in the case that a token's validity has lapsed after a period with no requests having been made to trigger a preemptive refresh.
I guess you could add a test for this with a very short expiry time and a short block in a test case.

* @private
* @returns {void}
*/
private calculateTimeForNewToken(accessToken): number {
// the time of expiration is found by decoding the JWT access token
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 to integrate calculateTimeForNewToken into saveTokenInfo because we now need to set both the expireTime and refreshTime from the values in the decoded token.

return timeForNewToken;
const { exp, iat } = decodedResponse;
// There are no required claims in JWT
if (!exp || !iat) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is just extra bullet-proofing for the case where the JWT does not have an exp or iat claim.

token_type: 'Bearer',
expires_in: 3600,
expiration: Math.floor(Date.now() / 1000),
access_token: CURRENT_ACCESS_TOKEN,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the iam-token-manager was changed to simply treat the access token as a JWT, none of the other fields in the IAM response are used, so we can eliminate them.


it('should refresh an access token without expires_in field', async done => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test was actually no longer valid, since the expires_in field is not used. It only happened to pass because the test did not set the expireTime in the instance.

@@ -33,7 +34,7 @@ describe('JWT Token Manager', () => {

const decodeSpy = jest
.spyOn(jwt, 'decode')
.mockImplementation(token => ({ iat: 0, exp: 100 }));
.mockImplementation(token => ({ iat: 10, exp: 100 }));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can't use iat = 0 because that triggers the new check in the jwt-token-manager.

@mkistler
Copy link
Contributor Author

mkistler commented Feb 5, 2020

I've updated the code to handle an expired token the same a missing token -- with a synchronous request for a new token. This turned out to be more invasive that I expected, but I think the new code now works as intended.

@mkistler mkistler requested review from dpopp07 and ricellis February 5, 2020 16:17
Copy link
Member

@dpopp07 dpopp07 left a comment

Choose a reason for hiding this comment

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

Looks good! 👍

Not sure if you want to consider this a feature or a fix but don't forget to use the commit header for semantic-release!

Copy link
Member

@ricellis ricellis left a comment

Choose a reason for hiding this comment

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

A couple of minor suggestions to take or leave, but nothing blocking a +1.

auth/token-managers/jwt-token-manager.ts Outdated Show resolved Hide resolved
test/unit/jwt-token-manager.test.js Outdated Show resolved Hide resolved
test/unit/jwt-token-manager.test.js Outdated Show resolved Hide resolved
test/unit/jwt-token-manager.test.js Outdated Show resolved Hide resolved
@mkistler mkistler force-pushed the mdk/serialize-token-refresh branch from cc10ba6 to 487a864 Compare February 14, 2020 03:06
@mkistler mkistler merged commit d908c0d into master Feb 14, 2020
@mkistler mkistler deleted the mdk/serialize-token-refresh branch February 14, 2020 03:21
ibm-devx-automation pushed a commit that referenced this pull request Feb 14, 2020
# [2.1.0](v2.0.4...v2.1.0) (2020-02-14)

### Features

* Pace token refresh requests to avoid rate-limiting ([#79](#79)) ([d908c0d](d908c0d))
@ibm-devx-automation
Copy link

🎉 This PR is included in version 2.1.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

JurajNyiri pushed a commit to JurajNyiri/node-sdk-core that referenced this pull request Aug 22, 2024
)

Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md)
- [Commits](prettier/eslint-plugin-prettier@v3.2.0...v3.3.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants