An access token is short-lived on purpose. A refresh token is how a session outlives it.
You only get one if offline_access was granted.
Refreshing#
/oauth2/tokenClientcurl -s -X POST https://account.ceagle.cc/oauth2/token \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN" \
-d client_id="$CLIENT_ID"A confidential client authenticates as well, with HTTP basic or with the secret in the body.
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 600,
"refresh_token": "…",
"scope": "openid profile email offline_access"
}They rotate#
That is the single most common integration bug with this endpoint. Write the new token before you do anything else with the response.
answer = POST /oauth2/token (grant_type=refresh_token, refresh_token=stored)
store(answer.refresh_token) # first
use(answer.access_token) # thenReuse is treated as theft#
Presenting a refresh token that has already been rotated ends the whole grant, not just that request. Every token issued under it stops working and the person has to sign in again.
That is deliberate. A rotated token showing up again means one of two things: your client lost the race and replayed an old value, or somebody else has a copy. Since the server cannot tell which, it assumes the worse one.
Refresh in one place
A single function, with a lock or a mutex around it. Two concurrent refreshes race, and the loser replays a rotated token.
Share the result
Other in-flight requests wait for the refresh already running rather than starting their own.
Persist before using
If the process dies between receiving a new token and storing it, the next start replays the old one.
Narrowing scope#
A refresh may ask for fewer scopes than were granted:
-d scope="openid profile"It can never ask for more. To widen, send the person through the authorization flow again.
Revoking#
/oauth2/revokeClientcurl -s -X POST https://account.ceagle.cc/oauth2/revoke \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d token="$REFRESH_TOKEN" \
-d token_type_hint=refresh_tokenRevoking a refresh token ends the grant. It answers 200 even for a token that was already invalid,
which is what the specification asks for and means you can call it without checking first.
From the person's side#
An account holder can see every application they have approved on their CEagle account pages, and withdraw any of them from there.
Withdrawing drops the grant and every token issued under it. Your application has to ask again.
Handle invalid_grant on refresh as "the session is over", not as an error to retry.
What to store#
refresh_tokenRequiredThe current one. Overwritten on every refresh.
access_tokenOptional. Cheap to obtain again, and short-lived enough that caching it barely helps.
expiryComputed from
expires_inat the time of the response, so you can refresh before a call fails rather than after.subThe subject from the id token. This, not the username or the email address, is the stable identity of the person.