Skip to main content

OAuth2 refresh token grant

In OAuth2 and OpenID Connect (OIDC) protocols, access tokens and ID tokens have an expiration time. When the token expires, the user needs to obtain a new token to continue accessing the protected resource. The process of obtaining a new token is called token refresh.

In this document, we explain how to refresh OAuth2 and OIDC tokens with Ory. We cover the refresh token, the requirements for obtaining a refresh token, the refresh token flow, refreshing access and ID tokens, refresh token rotation, and security protections.

info

The JavaScript example code contained in this article is exemplary and explains what happens under the hood. Everyone should use tried and tested open source libraries to consume OAuth2 and OpenID Connect. Writing this code by oneself should not be done, as you would not write your own SHA512 library.

The refresh token

The refresh token is a special token that can be used to obtain a new access token or ID token without the user's involvement. The refresh token is issued to the client during the initial token issuance and can be used to obtain a new token when the current token expires.

Refresh token flow

Here's a sequence diagram that shows the refresh token flow:

In the refresh token flow, the client sends a request to the authorization server with the refresh token. The authorization server checks if the refresh token is valid and if it is, issues a new access token or ID token to the client.

Requirements for obtaining a refresh token

To obtain a refresh token, the client needs to request the offline_access scope during the initial token issuance. The offline_access scope indicates that the client needs a refresh token.

Not all OAuth2 and OIDC flows support refresh tokens. Here's a table that shows which flows support refresh tokens:

FlowSupports Refresh TokenRequired scopeRequired response type
Authorization code flowYesoffline_accesscode
Implicit flowNo
Client credentials flowNo

Refreshing an access token

When a client refreshes an access token, the old access token becomes invalid, and only the new token is valid. The client can continue accessing the protected resource with the new access token.

Here's an example of how to refresh an access token with Ory:

// Set up the endpoint and refresh token
const endpoint = "https://oauth2.example.com/token"
const refreshToken = "<refresh token>"
const clientId = "<client id>"
const clientSecret = "<client secret>"

const params = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
scope: "scope1 scope2",
client_id: clientId,
client_secret: clientSecret,
})
// Send a POST request to refresh the access token
fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
})
.then((response) => {
if (!response.ok) {
throw new Error("Failed to refresh access token")
}
return response.json()
})
.then((data) => {
console.log("New access token:", data.access_token)
console.log("New ID token:", data.id_token)
console.log("New refresh token:", data.refresh_token)
})
.catch((error) => {
console.error(error)
})

Refreshing an ID token

When a client uses a refresh token to obtain a new access token, the authorization server may also issue a new ID token if the original token exchange included an ID token.

The new ID token has an updated expiry time but retains the same auth_time (time when the user authenticated). The auth_time claim in the ID token is used to determine if the user's authentication session is still active.

Refresh token rotation and security protections

Refresh tokens are single-use only, meaning that they become invalid after the first use. Every time a client uses a refresh token to request access tokens, a new refresh token is issued, and the previous token is invalidated. This mechanism adds another layer of security and makes it more difficult for attackers to use stolen refresh tokens.

To use refresh tokens, the client needs to store the refresh token securely. Refresh tokens are long-lived, which means they can be used for an extended period. Therefore, it's essential to store the token securely and protect it from unauthorized access. You can consider the following storage layers:

  • httpOnly and secure cookies (recommended)
  • OS keychain when on mobile apps (recommended)
  • Window.localStorage (acceptable)

Change refresh token lifespan

You can adjust the Refresh Token lifespan (TTL) using the /ttl/refresh_token configuration key. By default, the TTL is set to 1 hour.

note

The maximum age of refresh tokens is 6 months. This means that refresh tokens must be rotated at least every 6 months.

ory patch oauth2-config {project.id} \
--replace "/ttl/refresh_token=\"900h\"" \
--format yaml

When using a custom UI for the consent screen, it's essential to include the offline_access scope in the list of grant_scope for a refresh token to be returned.

Here's an example of how to include the offline_access scope in the list of grant_scope when using a custom UI for the consent screen:


import { Configuration, OAuth2Api } from "@ory/client"

const ory = new OAuth2Api(
new Configuration({
basePath: `https://${process.env.ORY_PROJECT_SLUG}.projects.oryapis.com`,
accessToken: process.env.ORY_API_KEY,
}),
)

export async function acceptConsent(consentChallenge: string) {
const { data } = await ory.getOAuth2ConsentRequest({ consentChallenge })

const grantScope = []

// If the client requested the "offline_access" scope, and the user consents,
// we add the "offline_access" scope to the scope to grant.
if (data.requested_scope.includes("offline_access")) {
// if (userGaveConsentForTokenRefresh) {
grantScope.push("offline_access")
// }
}

return await ory
.acceptOAuth2ConsentRequest({
consentChallenge: consentChallenge,
acceptOAuth2ConsentRequest: {
grant_scope: grantScope,
},
})
.then(({ data }) => data)
}