microsoft-authentication-library-for-js: MSAL does not return the new token after an acquireSilent()
I’m submitting a…
[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Other... Please describe:
Browser:
- Chromium version 74 (with Brave)
- Firefox version XX
- IE version XX
- Edge version XX
- Safari version XX
Library version
Library version: 1.0.1
Current behavior
- I’m using MSAL with VueJS
- I use the localStorage (and not the sessionStorage)
- I use axios to intercept outgoing requests to my API, and inject the
accessToken
fromacquireTokenSilent()
as a bearer token
When the token expires, MSAL fetches a new one (using acquireTokenSilent()
), updates the localStorage accordingly, but still returns the old one. As a result, when I call my API, I have 401 errors.
- When I refresh the page,
acquireTokenSilent()
returns the new token. - When I open a new tab from the same app, the new tab
acquireTokenSilent()
returns the new token. However, the old tab still uses the old token
The localStorage just after a login:
After a token refresh:
The new token is present (in green), while the old one is still there in red. MSAL still returns the old one when calling acquireTokenSilent()
Expected behavior
MSAL should use the new token
Minimal reproduction of the problem with instructions
The ajax interceptor:
axios.interceptors.request.use(
async request => {
await setToken(request)
return request
},
error => {
return Promise.reject(error)
}
)
const setToken = async (request: any) => {
const res = await authService.getAuthResponse() as Msal.AuthResponse
request.headers.common['Authorization'] = `Bearer ${res.accessToken}` // This fails if MSAL requested a new token
}
The authService:
public async getAuthResponse(): Promise<null | Msal.AuthResponse> {
const tokenRequest = {
scopes: this.graphScopes
}
try {
return await this.userAgentApp.acquireTokenSilent(tokenRequest)
}
catch (e) {
this.userAgentApp.acquireTokenRedirect(tokenRequest)
return null
}
}
UPDATED WORKAROUND:
To manually retrieve the correct token:
function extractMSALToken() {
const timestamp = Math.floor((new Date()).getTime() / 1000)
let token = null
for (const key of Object.keys(localStorage)) {
if (key.includes('"authority":')) {
const val: any = JSON.parse(localStorage.getItem(key)!)
if (val.expiresIn) {
// We have a (possibly expired) token
if (val.expiresIn > timestamp && val.idToken === val.accessToken) {
console.log(key)
// Found the correct token
token = val.idToken
}
else {
console.log('will remove ' + key)
// Clear old data
localStorage.removeItem(key)
}
}
}
}
if (token) return token
throw new Error('No valid token found')
}
Old workaround (invalid):
const setToken = async (request: any) => {
// Make sure that MSAL token is up-to-date
const res = await authService.getAuthResponse() as Msal.AuthResponse
// DIRTY FIX : directly fetch the token from the localStorage
request.headers.common['Authorization'] = `Bearer ${localStorage.getItem('msal.idtoken')}`
// request.headers.common['Authorization'] = `Bearer ${res.accessToken}` // This doesn't work
}
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 2
- Comments: 33 (16 by maintainers)
Guys,
This is a severe issue with a Microsoft product. Can we please get an update and an ETA to fix?
Cheers
Updated workaround: getting
msal.idtoken
from the localStorage does not work reliably, as the value is sometimes (often) not correctly updated by msal.This function seems to get the correct, up-to-date token more reliably:
I’m experiencing the same issue, which is causing my app to stop working every 24 hours (when token expires).
As a workaround I have to manage token expiration and clearing localstorage with dedicated code, which is far from ideal (and duplicates what MSAL should be doing) - the workaround could stop working anytime MSAL decides to use different storage keys, ie. I would prefer not knowing how the lib use localstorage and not deal with internal implementation details.
Could you provide an ETA for the fix?
@scambier Ah, that would explain why you are getting back an ID token. To properly protect a custom web API using AAD access tokens, you need to register a custom scope in the Azure Portal, and then pass that custom scope to your login call as well as
acquireTokenSilent
. Your web API will need to use one of our middlewares to verify the access token.See this comment for more info: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/1040#issuecomment-540810355
@sameerag actually, it seems to still not work as expected, but maybe I’m misunderstanding. My setup is still the one described in the first post, but MSAL still often fails to refresh the token by itself.
Basicall, each ajax request I do is preceded by a call to this function:
So, 401 responses shouldn’t happen, right? Because they still happen. I have to intercept those responses, call acquireTokenSilent/Popup() once more, inject that new token, and replay the request.
Edit: it’s almost like the
acquireSilent()
is sending a false positive “your token is still fresh”, when actually it should have caught an error to subsequently callacquirePopup()
oracquireRedirect()
Edit 2: the minimal code for my axios response interceptor:
@sameerag I’ve updated a few hours ago, and so far the token seems to refresh itself correctly, thanks.
@mxswat Yes, if you are using access tokens for a custom API (e.g. an Azure Function), you should create a custom scope.
@jasonnutter I was wrong on my assumptions: the accessToken is not invalid, it’s null. So yeah, it’s normal to have a 401 response, since I’m sending “Bearer null” as my Authorization. But I guess it’s not normal to have a null
accessToken
However, everything else in the
acquireToken()
response is valid, even theidToken.rawIdToken
. So when I re-queryacquireToken()
, theaccessToken
is correctly filled, and is identical to the previousidToken.rawIdToken
.This was the response I got today at 09:48 GMT. The expiration value is good (valid until 10:41 GMT)
Edit: what’s the role of
idToken.rawIdToken
?Adding this bug to our October 2019 miletsone
@tylerjwatson I have two objects containing an
access_token
in the localStorage (cf. my second screenshot), and their keys (that are also objects) don’t make them easy to retrieve and compare, so I’m just picking themsal.idtoken
value. Since it’s a workaround, I didn’t give it much thought and went with the first “it seems to work” solution