Microsoft-authentication-library-for-js: localStorage `expiresIn` out of sync with JWT `exp`

Created on 30 Jan 2020  路  2Comments  路  Source: AzureAD/microsoft-authentication-library-for-js

Library

Framework

No framework.

Description

We鈥檙e seeing instances where MSAL's expiresIn value stored in localStorage differs quite a bit from the exp value in the JWT token (sometimes in the minutes, sometimes in the hours/days range).

If a user gets into a state where exp is in the past, and expiresIn is in the future, calling acquireTokenSilent will consistently give you an expired token (cause it checks the value in expiresIn, and not the value in exp), so there's no way for a user to get out of that state without manually deleting localStorage.

The root cause is that exp value is calculated on the server, whereas expiresIn is calculated after-the-fact on the client, using their system clock.

We've seen the biggest difference between the two values when users put our tab in the background, or their computers to sleep. Chrome seems to throttle/pause code execution and/or network requests, and if that happens between the time you get a token back from the server, and the time when expiresIn value is calculated, you can get huge drift. We had a user whose JWT exp value was a Friday, and his localStorage expiresIn value was a Monday.

This is where MSAL.js relies on the system clock: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/1212c640aa619718328a5f6931f14ecf27ea0b36/lib/msal-core/src/UserAgentApplication.ts#L1381

Security

No.

Regression

No. This problem is even present in ADAL.js. Here's where ADAL.js relies on the system clock: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/de375da0f438018f01c6902f6dcfaedb24f91320/lib/adal.js#L1661

Configuration

{
  auth: {
    clientId: "e25ff63c-2374-438a-9fbe-91cd5ee67bae",
    authority: "https://login.microsoftonline.com/organizations",
    redirectUri: "http://localhost:8080/"
  },
  cache: {
    cacheLocation: "localStorage",
    storeAuthStateInCookie: true
  }
}

Reproduction steps

  1. Tweak the quickstart example to acquire tokens on a setInterval
  2. Clear localStorage periodically, so that you force the server to send you a new token
  3. Mess around with network download latencies, and see the drift
  4. Mess around with the system clock, and see the drift
  5. Put your computer to sleep at just the right time (kinda hard), and see the huge drift

Expected behavior

No drift, or a way to get out of this scenario.

  • No drift: This would require dropping expiresIn, and just relying on JWT's exp value. I hear you're working on encrypting tokens, so this may not be possible.
  • A way to get out of this scenario: If MSAL thinks a token should be valid, but our code is getting 401s consistently from our APIs, we should have a way of skipping MSAL's cache, and having it give us a fresh token.

Browsers

We see it happen relatively frequently with Chrome, but I see no reason why it wouldn't affect other browsers.

bug known-issue

Most helpful comment

Excellent find, Daniel! We recently ran into the same issue. It happens pretty rarely but for our app it results in pretty bad user experience. Hopefully this is treated with high priority, looking forward to the patched version.

All 2 comments

Excellent find, Daniel! We recently ran into the same issue. It happens pretty rarely but for our app it results in pretty bad user experience. Hopefully this is treated with high priority, looking forward to the patched version.

POC fix in PR referenced above, feedback welcome.

Was this page helpful?
0 / 5 - 0 ratings