Superseded: 31 January 2022 - Action required - Deprecating persistent refresh tokens

Hey @NusratSultana,

Thanks for your reply. I verified that there are no scope changes. (Note that YOUR_SCOPES is in the Atlassian response, so I actually can’t change it by accident.) I also verified that I used NEW_REFRESH_TOKEN.

I was not able to reproduce this now with the steps provided above. But it does reproduce with the following steps:

  1. Perform OAuth flow including offline_access as scope to get the initial refresh token.
  2. First refresh round:
curl --request POST \
  --url 'https://auth.atlassian.com/oauth/token' \
  --header 'Content-Type: application/json' \
  --data '{ "grant_type": "refresh_token", "client_id": "ID", "client_secret": "SECRET", "refresh_token": "ORIGINAL_REFRESH_TOKEN" }'

Response:

{"access_token":"NEW_ACCESS_TOKEN","refresh_token":"NEW_REFRESH_TOKEN","scope":"YOUR_SCOPES offline_access","expires_in":3600,"token_type":"Bearer"}
  1. Wait 20 seconds.
  2. Send the request from 2 with ORIGINAL_REFRESH_TOKEN again.
    Response:
{"error":"invalid_grant","error_description":"Unknown or invalid refresh token."}
  1. Send the same request as in 2, just with ORIGINAL_REFRESH_TOKEN replaced with NEW_REFRESH_TOKEN.
    Response:
{"error":"invalid_grant","error_description":"Unknown or invalid refresh token."}

If you leave out steps 3 and 4, it works. With those steps, it fails. Frankly, I’m not 100% sure whether I might not have actually performed steps 3 and 4 a few days ago when I made my original post to verify that the original refresh token got invalidated. That a failed attempt to use an old refresh token invalidates the new refresh token is surprising to me.

Another surprising behavior is that if you leave out step 3, step 4 succeeds. More generally, if you send refresh requests in quick succession with the same refresh token, you get multiple success responses with all different new refresh tokens. I just got 4 different refresh tokens when sending the same original refresh token 4 times in about 4 seconds. For me, the original refresh token starts being rejected about 5 seconds after I exchanged it for the first time. If I don’t send the original refresh token after 5 seconds, i.e. I don’t send any failing requests, then I observe that the first of the new refresh tokens is valid (which is tricky to find out because testing another new refresh token invalidates the first new refresh token, leaving you with no valid refresh token).

I wonder whether this is intended behavior to support retries or whether it’s an artifact of your implementation. Supporting retries certainly would make sense IMO: Imagine I need to refresh the token but then storing the new refresh token fails on my side. So if it’s intended that an old refresh token is accepted for some time after being refreshed, I would suggest 3 things to make it viable for clients:
A. Give the client more time than just 5 seconds. Maybe the retry would depend on the end-user retrying or maybe there is a queuing system with back-off in between, so 5 seconds might not suffice.
B. Ideally, return the same new refresh token (or make all returned refresh tokens valid).
C. If B is not possible, make the last instead of the first new refresh token the one that is valid.

1 Like