Skip to content

Refresh token may be reused after rotation between app and File Provider, causing invalid_grant #43

@croessner

Description

@croessner

Title

[BUG] Refresh token may be reused after rotation between app and File Provider, causing invalid_grant

Steps to reproduce

  1. Configure the iOS app against an OpenCloud server that uses an external OIDC provider.
  2. In this setup, the external IdP is Nauthilus.
  3. Log in successfully on iPhone.
  4. Let the app run for some time so token refresh happens in the background.
  5. Use both the main app and the File Provider extension over time.
  6. Eventually one of the clients fails with invalid_grant during grant_type=refresh_token.

Expected behavior

A refresh token issued to the iOS app should remain usable until it is rotated, and all iOS components sharing the same account state should consistently use the newest refresh token.

Actual behavior

After a successful refresh, a later refresh request is sent again with an older refresh token, and the IdP responds with:

{"error":"invalid_grant"}

After that, the app reports authorization failure and may consider the refresh token invalid until the account is re-authenticated.

Why this looks different from existing issues

This does not look like a pure external IdP discovery/client configuration issue such as:

It also looks more specific than the general "session is not refreshed" report in #16.

From the logs, this looks like a stale refresh token is reused after rotation, possibly between:

  • the main iOS app (OpenCloud)
  • the File Provider extension (OpenCloud File Provider)

Evidence from logs

A successful refresh returned a new refresh token at:

  • 2026-04-14 11:28:45 +0200

Later, that same newly issued refresh token was reused and rejected with invalid_grant at:

  • 2026-04-14 11:34:01 +0200
  • 2026-04-14 12:07:27 +0200

The interesting part is that the failing requests appear to come from different iOS processes:

  • OpenCloud File Provider
  • OpenCloud

That suggests token rotation may succeed in one process, but the updated refresh token is not reliably persisted or synchronized to the other process.

Example client log pattern

Successful refresh:

  • old refresh token used
  • the IdP returns a new refresh token

Later failure:

  • the same refresh token that was previously issued is sent again
  • the IdP returns 400 {"error":"invalid_grant"}

After that, the client starts logging messages like:

  • Authorization failed. (error 3, openid-connect: invalid_grant)
  • Previous token refresh attempts indicated an invalid refresh token

Environment

  • iOS version: 26.4.1
  • OpenCloud app version: 1.0.1 (build 3)
  • Device: iPhone
  • OpenCloud server with external OIDC provider
  • External IdP: Nauthilus
  • Same account used in app and File Provider extension

Important note

On the IdP side, the token endpoint behavior appears correct.

The Nauthilus logs show that the failing requests are grant_type=refresh_token requests returning 400, and the server-side analysis indicates that the rejected token was already invalid, expired, or already rotated.

This suggests the problem is likely on the iOS client side, in how the latest refresh token is stored and shared across processes.

Question / hypothesis

Could there be a race or persistence problem in the shared account/token storage between the main app and the File Provider extension, where one process rotates the refresh token but another process still uses the previous value?

Metadata

Metadata

Assignees

No one assigned

    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