I'm developing a web application that uses tokens for authentication. Users often open multiple browser tabs, and each tab has a client-side timer to refresh the authentication tokens before they expire. The tokens are stored in cookies and are set via the backend (Golang). We use refresh tokens to extend the current session by issuing a new refresh token and revoking the previous one.
The problem:
When two or more tabs simultaneously send a refresh request using the same refresh_token, the first request succeeds, and the server issues new tokens while revoking the old refresh_token. Subsequent requests from other tabs using the now-revoked refresh_token fail with a 401 Unauthorized error, causing those tabs to log the user out.
Managing synchronization across tabs adds complexity, and I'm not sure if this needs to be handled server-side or client-side.
We aim to keep the user logged in while they are active. When the user closes the website, we want to keep the session alive for about 5 more minutes so that if the user returns, they don't have to log in again. The main logic of our website uses a WebSocket (only in certain tabs) to transfer data between the client and server, and no API calls are being made during this time, so the user session can expire. To address this, we decided to call the refresh API every 30 seconds to keep the user logged in, which exacerbated the issue of simultaneous refresh API calls and the associated bugs. Additionally, we need to monitor the user session and terminate it when appropriate, so we cannot use stateless tokens.
Are there alternative strategies to handle token refresh in a multi-tab environment that avoid these synchronization issues?
Would it be feasible to remove refresh tokens altogether and rely on short-lived access tokens with traditional server-side sessions? What are the trade-offs in terms of security, scalability, and user experience?
Backend: Golang Frontend: React I'm looking for guidance on how to redesign our authentication flow or adopt a different approach that gracefully handles multiple tabs without conflicting refresh requests, while ensuring security and a seamless user experience.