Is the 1-hour refresh-token retry window a stable contract, or will it change to enforce newer RFC's?

We’re migrating a public app to expiring offline access tokens and building the refresh path around the documented retry behavior. Before we depend on it, we want to know whether it is stable.

The docs say:

For offline tokens, Shopify returns the same refreshed token response for up to 1 hour after the original refresh, so a refresh interrupted by a transient outage can be safely retried once connectivity is restored.

We confirmed it against a dev store. Presenting an already-redeemed refresh token inside the window returns the same access_token and the same refresh_token, with a distinct x-request-id per call and cache-control: no-store / cf-cache-status: DYNAMIC, so it is genuinely re-served rather than cached. A token two generations behind returns 401 invalid_request, “This request requires an active refresh_token”. We have raw request and response captures and can share them if useful.

We also confirmed the token endpoint is not sender-constrained: the TLS handshake sends no CertificateRequest (so no mTLS per RFC 8705), and no DPoP proof is involved (RFC 9449).

That combination is what prompts the question. RFC 9700 section 4.14.2 says:

Authorization servers MUST utilize one of these methods to detect refresh token replay by malicious actors for public clients:

  • Sender-constrained refresh tokens: the authorization server cryptographically binds the refresh token to a certain client instance, e.g., by utilizing [RFC8705] or [RFC9449].

  • Refresh token rotation: the authorization server issues a new refresh token with every access token refresh response. The previous refresh token is invalidated, but information about the relationship is retained by the authorization server. If a refresh token is compromised and subsequently used by both the attacker and the legitimate client, one of them will present an invalidated refresh token, which will inform the authorization server of the breach. The authorization server cannot determine which party submitted the invalid refresh token, but it will revoke the active refresh token. This stops the attack at the cost of forcing the legitimate client to obtain a fresh authorization grant.

Our questions:

  1. Is the 1-hour same-response window a contract we can build on for the foreseeable future, or is it subject to change?

  2. Is there work planned to move toward 4.14.2, either sender-constraining, or rotation where a reused refresh token revokes the active one?

The reason we ask is that the two answers lead to opposite designs, and the second one is expensive for us to get wrong. Under the current behavior, a retry after an interrupted refresh recovers quietly. Under 4.14.2 rotation, that same retry would revoke the store’s active token and force a fresh authorization grant, which for us means waiting for a merchant to reopen the app, which they may not ever actually do. It technically goes beyond just legit transient retries, any app with concurrent workers can accidentally use the same refresh tokens.

The 1 hour retry mechanism for refresh tokens was implemented in response to concerns raised in the other threads (see: Action required: existing public apps must use expiring offline access tokens by January 1, 2027 - #29 by Karthick_GM )

Basically, the concern here is for apps which run lots of backend processes without user interaction, should shopify’s oauth fail (and it has), this totally breaks apps without offering any sort of recovery.

Whilst the 1 hour retry is good that it somewhat alleviates these concerns, it still does not address what if shopify goes down for more than 1 hour (which it has before too).

The current implementation is more suited for user interaction workflows rather than automated backend API calls.

Agreed, it’s the automated backend behavior that has us more concerned for this reason, but also just from a general security awareness standpoint. If this kind of behavior is ever classified as a replay attack and the active access/refresh tokens get revoked, there is no automated recovery, even if it was never truly a nefarious attack and just something like a retry or concurrent request.

A strong commitment from Shopify about their security position and assurances that there will always be some kind of refresh-replay as a feature would go a long way to make any backend apps we are building more durable without complex architectural systems to enforce at-most-once refresh behavior.