After an access token expired, I submitted its refresh token twice. XenForo 2.3.12 issued a new access/refresh pair both times.
The two access tokens were different and carried the same user and scopes. The old refresh token remained usable after both rotations.
Expiry broke cleanup
The refresh endpoint created the replacement pair, then called the old access token’s revocation routine. Revocation began with the access token’s normal validity check.
An expired token failed immediately. The function returned before setting a revocation timestamp and before revoking the related refresh token.
Once the access token expired, its longer-lived refresh token could be used repeatedly to create new pairs. Exploitation required that refresh token and, for confidential clients, the client secret. New tokens kept the scopes from the original grant.
I tested build 2031270 after waiting for the parent access token to expire. The PoC makes the two refresh requests and can check both access tokens at /api/me.
XenForo 2.3.13 consumes the refresh token independently of its parent access token. Consumption and replacement issuance now happen as one operation, covering the expired-token path and simultaneous requests.