Skip to content

Parallel token refreshes reuse the same refresh token, causing logouts with IdPs that rotate refresh tokens #235

Open

Description

@rcdailey

Steps to reproduce

  1. Use an external OIDC provider that rotates refresh tokens and revokes the session when an old refresh token is reused (I'm using Pocket ID v2.14.0, which does this since v2.9.0; see 🐛 Bug Report: Reusing a rotated refresh token revokes the whole session since v2.9.0 pocket-id/pocket-id#1789).
  2. Log in with the Android app (v1.2.5).
  3. Use the app normally (browsing with thumbnails, sync, available offline) until the access token needs refreshing.

Expected behavior

One refresh request is sent, and every other caller waits for it and uses the new tokens.

Actual behavior

Every few days the app sends a burst of refresh requests with the same refresh token. The first reuse makes the IdP revoke the session, and the app asks me to log in again. From my Pocket ID logs for one logout: 1 successful refresh, then 30 failed refreshes (HTTP 400) within 10 seconds, then a new login.

Looking at the v1.2.5 source, I think this is a race in AccountAuthenticator:

  • getAuthToken refreshes whenever peekAuthToken returns null (AccountAuthenticator.java#L175). Callers include SingleSessionManager.getClientFor, ConnectionValidator.checkUnauthorizedAccess after a 401, and the Coil thumbnail interceptor on every image request.
  • refreshToken() reads the refresh token (L325), does the network call, and only then stores the new one (L441). Nothing locks or deduplicates this, so two callers can send the same token.
  • After a failed refresh (L420), the old refresh token stays stored and the access token stays empty, so each following caller tries again with the same dead token. I believe that explains the 30 requests.
  • Every refresh failure (network error, 5xx, or invalid_grant) goes down the same path to the login screen.

With IdPs that don't revoke on reuse, only the losing request fails, which is probably why this hasn't shown up much. The iOS app had the same class of problem between the app and its File Provider (opencloud-eu/ios#43), fixed there by locking the refresh and re-checking the stored token after getting the lock.

Environment

  • App: OpenCloud Android 1.2.5
  • Server: OpenCloud 8.0.1 with an external IdP (Pocket ID v2.14.0), public client OpenCloudAndroid with PKCE

Activity

  1. self-assigned this
    on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions