Describe the bug
The Send web app's OIDC access-token refresh design causes uncontrolled, concurrent silent refreshes and spurious logouts inside Thunderbird.
Three factors combine:
automaticSilentRenew: true schedules background renewals, and getAccessToken() also calls userManager.signinSilent() on demand whenever the token is expired. There is no deduplication, so concurrent callers (an api.call before a request, checkAuthStatus, the 60s add-on menu timer, and the concurrent parts of a file upload) each fire their own signinSilent().
- With refresh-token rotation, every extra concurrent refresh races the others; the losers use an already-rotated refresh token and fail with
invalid_grant, which reads as a spurious logout.
loadUserInfo: true adds a userinfo round-trip after every refresh. That call can fail inside Thunderbird and reject an otherwise-successful token refresh. The library's fallback also uses an iframe flow that cannot work inside Thunderbird.
The visible symptoms are a storm of token / userinfo requests, transient auth failures that surface as spurious logouts, and downstream breakage such as GET users/me failing at load (leaving the user store unpopulated — see companion upload issue).
To Reproduce
Steps to reproduce the behavior:
- Use Send inside Thunderbird past the short-lived access-token expiry (~5 min).
- Trigger multiple concurrent authenticated requests (e.g. a multi-part file upload, or normal dashboard load with the menu timer running).
- Observe repeated
token and userinfo requests and intermittent logout / "not signed in" behavior even though the session (refresh token) is still valid.
Expected behavior
A single in-flight refresh is shared by concurrent callers. A transient failure (network/timeout/userinfo) keeps the session intact and is retried later; only a genuine auth failure (refresh token revoked/expired, e.g. invalid_grant / login_required / session_expired) ends the session and notifies the add-on to revert its menu to logged-out.
Actual behavior
Multiple concurrent signinSilent() calls race under refresh-token rotation; losers fail with invalid_grant. userinfo failures reject otherwise-successful refreshes, and the iframe fallback cannot run in Thunderbird. The result is a token/userinfo request storm and spurious logouts / unpopulated session state.
Screenshots
Network tab shows a large number of repeated token (POST) and userinfo (GET) requests.
System
- OS: macOS
- Browser Version: Thunderbird (add-on / system add-on build)
Additional context
Fixed on branch fix/addon-expired-token-closes-send-tabs in packages/send/frontend/src/stores/auth-store.ts:
automaticSilentRenew: false and loadUserInfo: false (profile claims already come from the id_token) to remove uncontrolled background renews and the fragile post-refresh round-trip.
- A shared in-flight
refreshAccessToken() promise dedupes concurrent refreshes.
- Distinguishes genuine auth failures (
invalid_grant, login_required, session_expired) — which clear login state and notify the add-on via SIGN_OUT — from transient failures, which preserve the session for a later retry.
This is the follow-up noted in PR #949 ("Posting SIGN_OUT on silent-refresh failure would let the menu revert to logged-out cleanly").
Describe the bug
The Send web app's OIDC access-token refresh design causes uncontrolled, concurrent silent refreshes and spurious logouts inside Thunderbird.
Three factors combine:
automaticSilentRenew: trueschedules background renewals, andgetAccessToken()also callsuserManager.signinSilent()on demand whenever the token is expired. There is no deduplication, so concurrent callers (anapi.callbefore a request,checkAuthStatus, the 60s add-on menu timer, and the concurrent parts of a file upload) each fire their ownsigninSilent().invalid_grant, which reads as a spurious logout.loadUserInfo: trueadds auserinforound-trip after every refresh. That call can fail inside Thunderbird and reject an otherwise-successful token refresh. The library's fallback also uses an iframe flow that cannot work inside Thunderbird.The visible symptoms are a storm of
token/userinforequests, transient auth failures that surface as spurious logouts, and downstream breakage such asGET users/mefailing at load (leaving the user store unpopulated — see companion upload issue).To Reproduce
Steps to reproduce the behavior:
tokenanduserinforequests and intermittent logout / "not signed in" behavior even though the session (refresh token) is still valid.Expected behavior
A single in-flight refresh is shared by concurrent callers. A transient failure (network/timeout/userinfo) keeps the session intact and is retried later; only a genuine auth failure (refresh token revoked/expired, e.g.
invalid_grant/login_required/session_expired) ends the session and notifies the add-on to revert its menu to logged-out.Actual behavior
Multiple concurrent
signinSilent()calls race under refresh-token rotation; losers fail withinvalid_grant.userinfofailures reject otherwise-successful refreshes, and the iframe fallback cannot run in Thunderbird. The result is atoken/userinforequest storm and spurious logouts / unpopulated session state.Screenshots
Network tab shows a large number of repeated
token(POST) anduserinfo(GET) requests.System
Additional context
Fixed on branch
fix/addon-expired-token-closes-send-tabsinpackages/send/frontend/src/stores/auth-store.ts:automaticSilentRenew: falseandloadUserInfo: false(profile claims already come from the id_token) to remove uncontrolled background renews and the fragile post-refresh round-trip.refreshAccessToken()promise dedupes concurrent refreshes.invalid_grant,login_required,session_expired) — which clear login state and notify the add-on viaSIGN_OUT— from transient failures, which preserve the session for a later retry.This is the follow-up noted in PR #949 ("Posting
SIGN_OUTon silent-refresh failure would let the menu revert to logged-out cleanly").