Skip to content

Keep identity tokens of parallel refreshes refreshable - #78

Merged
robertlemke merged 1 commit into
mainfrom
bugfix/parallel-refresh-logout
Sep 15, 2026
Merged

robertlemke merged 1 commit into
mainfrom
bugfix/parallel-refresh-logout

Conversation

@robertlemke

@robertlemke robertlemke commented Sep 15, 2026

Copy link
Copy Markdown
Member

With version 6.0.0, users get logged out about an hour after their identity token was refreshed, although their session is still valid. The security log says that the refresh token in the session "belongs to another identity token". This should go into a 6.0.x patch release.

The cause is a race between parallel requests, for example HTMX polls or several tabs. If the identity token has just expired, each request refreshes it on its own and receives a different identity token. The session keeps the result of the request which wrote last, but the browser may keep the cookie of another one, and that token can't be refreshed an hour later.

The refresh token in the session is now bound to a generation of identity tokens: all tokens which requests received by refreshing the same predecessor. Any token of the current generation can be refreshed. Before storing a refreshed token, the provider reads the session again and joins a generation which a parallel request stored meanwhile, and it confirms the write by reading once more. If the session was logged out or logged in again during the refresh, the refreshed token is discarded. The security properties of 6.0 stay the same, and session data of 6.0.0 is still read.

Flow's session storage has no compare-and-set, so a small window for lost updates remains. With refresh token rotation, the reuse interval of the identity provider must cover such a burst of parallel refreshes.

The refresh token in the session is now bound to a generation of
identity tokens: the token of the login, or all tokens which requests
received by refreshing the same predecessor generation. Any token of
the current generation can be refreshed, and requests with a token of
the previous generation receive the latest one for ten minutes.

Previously, the session remembered only the identity token which was
stored last. Browsers send several requests in parallel, for example
polls or fragment loads. If the identity token expired just before
such a burst, several requests refreshed it at the same time. The
session kept the result of the request which wrote last, while the
browser could keep the cookie of another response. That token worked
until it expired about an hour later. Its refresh was then rejected
with "belongs to another identity token", and the user was logged out
although the session was still valid.

Before storing a refreshed identity token, the provider now reads the
session data again. Flow reads session data from the storage on every
call, so the provider sees what parallel requests stored while it
waited for the identity provider. If another request refreshed the
same generation meanwhile, the new token joins that generation and its
refresh token replaces the stored one. After writing, the provider
reads once more and repeats the merge if a parallel write replaced its
token. If the session no longer accepts the expired token, for example
after a logout or another login during the refresh, the refreshed
token is discarded and the request is not authenticated.

Only tokens which were issued for this session join a generation, so
an older identity token together with a stolen session stays useless.
Session data stored by version 6.0.0 is still accepted.
@robertlemke
robertlemke force-pushed the bugfix/parallel-refresh-logout branch from 920872b to a677882 Compare September 15, 2026 12:45
@robertlemke
robertlemke merged commit f624460 into main Sep 15, 2026
12 checks passed
@robertlemke
robertlemke deleted the bugfix/parallel-refresh-logout branch September 15, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant