I'm having an issue where I do not find a security-first and maintenable answer anywhere.
Imagine a dashboard doing multiple queries at the same time, how do you handle refresh_tokens in a clean and stadard way?
The stack is (even if the stack doesn't matter here):
Backend - Laravel with a JWT token authentification Frontend - Vue JS with axios for the API calls
Endpoints:
- /auth/login (public)
- /auth/refresh-token (need auth)
- /statistics (need auth)
- /other-statistics (need auth)
- /event-more-statistics (need auth)
- /final-statistics (need auth) ...
JWT refresh workflow
- User navigates to mywebsite.com/login on the client
- Login page does an API call to the server
axios.get('/auth/login').then(res => setTokenAndUser(res))
- Server responds with access_token (lifetime 1min) and refresh_token (lifetime 1 month or so)
- User navigates to mywebsite.com/dashboard
- User clicks on something, Dashboard page does 4 API calls in parallel to the 4 last endpoints above
// ... just some pseudo code
userDidAction() {
axios.get('/statistics').then(res => handleThis(res.data));
axios.get('/other-statistics').then(res => handleThat(res.data));
axios.get('/event-more-statistics').then(res => handleThisAgain(res.data));
axios.get('/final-statistics').then(res => handleThatAgain(res.data));
}
// ...
- 1st call finishes, server invalidates old tokens + responds with new access_token & refresh_token
- 2nd call is blocked by server because it's transporting an outdated token
- 3rd call is blocked by server because it's transporting an outdated token
- 4th call is blocked by server because it's transporting an outdated token
- Client / UI is not updated correctly
This is a very common scenario on SPAs and SaaS apps. Having multiple asynchronous API calls is not an edge case.
What are my options here ?
- not invalidating the tokens :
- but then there's a security breach and using JWT tokens becomes useless
- keeping track of each API calls that failed and replays them when the refresh token changes
- this is hard to maintain and creates unpredictable behaviours on the UI for the user
- if the user interacts during the call replays it would messeup the call handlers
- each axios call has a promise, to expect a good handling we would need to store and delay each promise too for the UI the be handled correctly
- each new replay would also re-create new tokens each time
My current idea is to make the access_token last 3 days and the refresh_token last a month with the following workflow :
- When the frontend starts, we check the access_token validity on the client-side
- if the refresh_token has expired, wipe out tokens from client
- else do nothing
- if the access_token expires in more than 12h, send all future request with it
- else use the refresh token to get new tokens
This makes the refresh_token travel less on the network and makes parallel fails impossible since we change tokens only when the frontend loads initially and therefore, tokens would live for at least 12h before failing.
Despite this solution working, I'm looking for a more secure / standard way, any clues?