How do I manage access token in Nodejs application?

别说谁变了你拦得住时间么 提交于 2020-12-06 16:25:45

问题


Click here to see Overview Diagram

Hi All, I have service A that needs to call service B in different network domain. To make a call to service B, service A gets access token from identity provider then call service B with the access token in Http Authorization header. When there are multiple or concurrent requests to service A, I want to minimize the calls to identity provider to get access token. So I plan to implement caching by using https://www.npmjs.com/package/lru-cache which is similar to the approach using by google-auth-library https://github.com/googleapis/google-auth-library-nodejs/blob/master/src/auth/jwtaccess.ts. The service A will call identity provider to get access token and store to the cache. When the next request come in, the service A will use the token from cache and calls service B. If the cache item is expired, then service A will get service token and store in cache.
I have the following questions:

  1. How do we handle race condition when there are concurrent request to service A that can cause multiple requests are sent to get access token and have multiple updates to the cache?
  2. Let say, access token have 1 hour expiry. How do we have mechanism to get a new token before the token is expired?

Any comments would be very appreciated. Thank you in advance.


回答1:


It sounds like you would benefit from a little singleton object that manages the token for you. You can create an interface for getting the token that does the following:

  1. If no relevant token in the cache, go get a new one and return a promise that will resolve with the token. Store that promise in the cache in place of the token.
  2. If there is a relevant token in the cache, check it's expiration. If it has expired or is about to expire, delete it and go to step 1. If it's still good, return a promise that resolves with the cached token (this way it always returns a promise, whether cached or not).
  3. If the cache is in the process of getting a new token, there will be a fresh token stored in the cache that represents the future arrival of the new token so the cache can just return that promise and it will resolve to the token that is in the process of being fetched.

The caller's code would look like this:

tokenCache.getToken().then(token => {
    // use token here
});

All the logic behind steps 1, 2 and 3 is encapsulated inside the getToken() method.


Here's an outline for a tokenCache class that hopefully gives you the general idea:

const tokenExpiration = 60 * 60 * 1000;    // 1 hr in ms
const tokenBeforeTime = 5 * 60 * 1000;     // 5 min in ms

class tokenCache {
    constructor() {
        this.tokenPromise = null;
        this.timer = null;
        // go get the first token
        this._getNewToken().catch(err => {
            console.log("error fetching initial token", err);
        });
    }
    getToken() {
        if (this.tokenPromise) {
            return this.tokenPromise().then(tokenData => {
                // if token has expired
                if (tokenData.expires < Date.now()) {
                    return this._getNewToken();
                } else {
                    return tokenData.token;
                }
            });
        } else {
            return this._getNewToken();
        }
    }

    // non-public method for getting a new token
    _getNewToken() {
        // for example purposes, this uses the got() library to make an http request
        // you fill in however you want to contact the identity provider to get a new token
        this.tokenPromise = got(tokenURL).then(token => {
            // make resolve value be an object that contains the token and the expiration
            // set timer to get a new token automatically right before expiration
            this._scheduleTokenRefresh(tokenExpiration - tokenBeforeTime);
            return {
                token: token,
                expires: Date.now() + tokenExpiration;
            }
        }).catch(err => {
            // up error, clear the cached promise, log the error, keep the promise rejected
            console.log(err);
            this.tokenPromise = null;
            throw err;
        });
        return this.tokenPromise;
    }
    // schedule a call to refresh the token before it expires
    _scheduleTokenRefresh(t) {
        if (this.timer) {
            clearTimeout(this.timer);
        }
        this.timer = setTimeout(() => {
            this._getNewToken().catch(err => {
                console.log("Error updating token before expiration", err);
            });
            this.timer = null;
        }, t);
    }

}

How do we handle race condition when there are concurrent request to service A that can cause multiple requests are sent to get access token and have multiple updates to the cache?

You store a promise and always return that promise. Whether you're in the middle of getting a new token or there's already a token in that promise, it doesn't matter. You return the promise and the caller uses .then() or await on the promise to get the token. It "just works" either way.

Let say, access token have 1 hour expiry. How do we have mechanism to get a new token before the token is expired?

You can check the token for expiration when it's requested and if it's expired, you replace the existing promise with one that represents a new request for the token.



来源:https://stackoverflow.com/questions/60731854/how-do-i-manage-access-token-in-nodejs-application

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!