How to use the refresh token with Cognito
Use the long-lived refresh token to generate new access tokens
Cognito tokens
When a client logs in to a Cognito user pool they get 3 tokens: a refresh_token
, an id_token
, and an access_token
. Later, when the client makes
requests to the backend it attaches the access_token
to the request. The backend API then checks the token and verify that it's valid, it's not tampered
with, and it's generated by the expected Cognito app client.
When using the authorization code flow (the recommended one for SPAs), the tokens are returned by the TOKEN
endpoint after exchanging the code
for them:
const res = await fetch(`${cognitoLoginUrl}/oauth2/token`, {
method: "POST",
headers: new Headers({"content-type": "application/x-www-form-urlencoded"}),
body: Object.entries({
"grant_type": "authorization_code",
"client_id": clientId,
"code": searchParams.get("code"),
"code_verifier": codeVerifier,
"redirect_uri": window.location.origin,
}).map(([k, v]) => `${k}=${v}`).join("&"),
});
if (!res.ok) {
throw new Error(await res.json());
}
const tokens = await res.json();
/*
tokens = {
access_token: "..."
expires_in: 3600
id_token: "..."
refresh_token: "..."
token_type: "Bearer"
}
*/
For the backend requests, the access_token
is sent in the Authorization header:
const apiRes = await fetch("/api/user", {
headers: new Headers({"Authorization": `Bearer ${tokens.access_token}`}),
});
Token expiration times
The three tokens are usable for different durations. You can configure these for the Cognito app client:
The access_token
and the id_token
are short-lived. You can not set them to be valid for more than 1 day and the default is 60 minutes.
The refresh_token
is long-lived. It can be valid for up to 10 years, and the default is 30 days.
Refreshing an access token
Since the access_token
expires regularly as the users interact with the backend, the client needs to generate new ones. This is what the
refresh_token
is good for.
Using the refresh_token
is a call to the same TOKEN endpoint the authorization code uses but the grant_type
is refresh_token
:
const res = await fetch(`${cognitoLoginUrl}/oauth2/token`, {
method: "POST",
headers: new Headers({"content-type": "application/x-www-form-urlencoded"}),
body: Object.entries({
"grant_type": "refresh_token",
"client_id": clientId,
"redirect_uri": window.location.origin,
"refresh_token": tokens.refresh_token,
}).map(([k, v]) => `${k}=${v}`).join("&"),
});
if (!res.ok) {
throw new Error(await res.json());
}
const newTokens = await res.json();
/*
newTokens = {
access_token: "..."
expires_in: 3600
id_token: "..."
token_type: "Bearer"
}
*/
The result does not include a refresh_token
, only an access_token
and an id_token
. Because of this, the client needs to relogin to get a new
refresh_token
when it expires.
Token expiration timing
Why this complication with the refresh_token
then? Why not Cognito returns just one token that is valid for the full duration of the client session?
First, you might store the refresh_token
in a different place. For example, you can implement a backend endpoint that stores it and generates
access_token
s for the client when it needs them. This way, the refresh_token
won't be stored in the browser.
Second, refresh_token
s and access_token
s can be revoked. But checking an access_token
if it's revoken or not for every API call is slow and
expensive as that requires an extra network call. On the other hand, if you use short expiration times for the access_token
s then they will be invalid
after revocation without an explicit check. And there is a validity check implemented by Cognito when the refresh_token
is used, so a revoken token won't
be able to generate new access_token
s.
Considerations
So, what's a good value for the token validity settings?
As usual, it's about tradeoffs. Let's see what the default of 60 minutes and 30 days mean!
- The client needs to refresh the token every hour (this is an automated process)
- A revoken token is valid for up to 60 minutes
- The user must relogin every month (this is a manual process)
If you want the user session to last longer, increase the validity of the refresh_token
.
And if you want token revocation to have a more immediate effect, you can either reduce the lifespan of the access_token
or implement per-call
revocation checking.
Conclusion
Cognito returns a refresh_token
when a user signs in along with an access_token
and an id_token
. It is a longer-lived token with that the
client can use to generate new access_token
s and id_token
s.