Cvmcosta / ltijs

Turn your application into a fully integratable LTI 1.3 tool provider.

Home Page:https://cvmcosta.github.io/ltijs/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Access token expiration race condition

slayybelle opened this issue · comments

Describe the bug
When using an access token near its time of expiration, ltijs will throw this error HTTPError: Response code 401 (Unauthorized)

Happens intermittently, but due to a recent addition of a cron job which ran hourly I was finally able to track this down.

Happens with calls to getLineItemById, getLineItems; I suppose anything that calls platform.platformAccessToken

Expected behavior
Refresh the access token; perhaps add an almost-empty flag to give some extra time to handle this case

Provider logs
See the commented section towards the bottom where I added code to track this condition where the token is within 10s of expiring.

This is from Platform.js:

async platformAccessToken(scopes) {
    const result = await (0, _classPrivateFieldGet2.default)(this, _Database).Get((0, _classPrivateFieldGet2.default)(this, _ENCRYPTIONKEY2), 'accesstoken', {
      platformUrl: (0, _classPrivateFieldGet2.default)(this, _platformUrl),
      clientId: (0, _classPrivateFieldGet2.default)(this, _clientId),
      scopes: scopes
    });
    let token;

    if (!result || (Date.now() - result[0].createdAt) / 1000 > result[0].token.expires_in) {
      provPlatformDebug('Valid access_token for ' + (0, _classPrivateFieldGet2.default)(this, _platformUrl) + ' not found');
      provPlatformDebug('Attempting to generate new access_token for ' + (0, _classPrivateFieldGet2.default)(this, _platformUrl));
      provPlatformDebug('With scopes: ' + scopes);
      token = await Auth.getAccessToken(scopes, this, (0, _classPrivateFieldGet2.default)(this, _ENCRYPTIONKEY2), (0, _classPrivateFieldGet2.default)(this, _Database));
    } else {
      provPlatformDebug('Access_token found');
      token = result[0].token;
  //=========================== I ADDED THIS CODE TO TEST:
    if (!result || (Date.now() - result[0].createdAt) / 1000 > result[0].token.expires_in - 10) {
            console.log("LTIJS: hit edge condition, token almost expired", result[0], Date.now() - result[0].createdAt);
    }
    }

here is what my logs looks like when a getMembers request fails. Note the time computed is 3599933 which is so close to the 3600000ms (expiration time), but is still treated as not-expired

LTIJS: hit edge condition, token almost expired {
  token: {
    access_token: '75aee939de83210f089d7259a728cf65',
    token_type: 'Bearer',
    expires_in: 3600,
    scope: 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly'
  },
  createdAt: 1677285068000
} 3599933
[2023-02-25T01:31:08.275Z] ERROR: xxxxx/service:lti:roster/2422158 on ip-172-16-0-249: ERROR retrieving roster for YYYYYY (err.code=ERR_NON_2XX_3XX_RESPONSE)
    HTTPError: Response code 401 (Unauthorized)
        at Request.<anonymous> (/usr/local/xxxxx/node_modules/got/dist/source/as-promise/index.js:118:42)
        at processTicksAndRejections (node:internal/process/task_queues:96:5)

Screenshots
If applicable, add screenshots to help explain your problem.

Ltijs version

  • Version 5.8.9

NodeJS version

  • v16.19.0

Platform used

  • Canvas

Hello @slayybelle very interesting find! Thank you. I am guessing the more straight forward solution for this is to not test the expiration time exactly but instead check if it's expiring soon. I will add this to this to my list of improvements and will get it done as soon as i can.