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.
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
Only Authorization Code (response_type=code
) and hybrid flows yield refresh tokens. They are always issued alongside an access
token during code exchange.
The client configuration must have the offline_access
scope in its list of allowed scopes, and the refresh_token
and
authorization_code
grant types enabled.
To obtain a refresh token, the client must request the offline_access
scope during the authorization request.
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
andsecure
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 30
days.
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 <project-id> --workspace <workspace-id> \
--replace "/ttl/refresh_token=\"900h\"" \
--format yaml
Custom consent UI
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)
}