The Core Concept: Why Use a Refresh Token?
Access Tokens are Short-Lived: They typically expire after a short period (e.g., 1 hour). This is a security measure. If an access token is stolen, it's only useful for a limited time.

Refresh Tokens are Long-Lived: They are issued alongside the access token and can live for days, weeks, or even months.
The Refresh Flow: When your access token expires, instead of forcing the user to log in again, your application can use the refresh token to get a brand new access token (and sometimes a new refresh token) silently in the background.
The Step-by-Step Flow
Here is the standard sequence of events:
Initial Authentication: The user logs in, and your app receives an access_token and a refresh_token.
API Request: You use the
access_tokento call a protected API.Token Expiration: The API returns a
401 Unauthorizederror because theaccess_tokenhas expired.Refresh Request: Your application sends the
refresh_tokento the authorization server's token endpoint.New Tokens: The authorization server validates the refresh token and, if valid, responds with a new
access_token(and optionally a newrefresh_token).Retry API Call: Your application retries the failed API request with the new
access_token.
The HTTP Request to Refresh the Token
You make a POST request to the token endpoint of the authorization server (e.g., Google, Auth0, Microsoft Identity Platform).
Request Details:
URL: The token endpoint (e.g.,
https://oauth2.googleapis.com/token,https://your-domain.auth0.com/oauth/token)Method:
POSTHeaders:
Content-Type: application/x-www-form-urlencodedBody (as x-www-form-urlencoded):
| Key | Value | Description |
|---|---|---|
grant_type | refresh_token | Required. Must be set to refresh_token. |
refresh_token | your_refresh_token_here | Required. The refresh token you originally received. |
client_id | your_client_id | Often required. Your application's identifier. |
client_secret | your_client_secret | Often required if it's a confidential client (e.g., a server-side app). Not used for public clients like a native mobile app. |
Code Examples
Here’s how to implement this in various programming languages.
1. Python Example (using requests)
import requests# Configuration - Replace with your detailstoken_endpoint = "https://oauth2.googleapis.com/token"client_id = "your_google_client_id"client_secret = "your_google_client_secret"refresh_token = "the_refresh_token_you_stored"# Prepare the request datadata = {
'grant_type': 'refresh_token',
'refresh_token': refresh_token,
'client_id': client_id,
'client_secret': client_secret,}# Headersheaders = {
'Content-Type': 'application/x-www-form-urlencoded'}# Make the POST requestresponse = requests.post(token_endpoint, data=data, headers=headers)# Check if the request was successfulif response.status_code == 200:
new_tokens = response.json()
new_access_token = new_tokens['access_token']
# The response might also include a new refresh_token
new_refresh_token = new_tokens.get('refresh_token', refresh_token) # Use new one if provided, else keep old
print("New Access Token:", new_access_token)
# IMPORTANT: Store the new_refresh_token if a new one was provided!else:
print("Error:", response.status_code, response.text)2. JavaScript (Node.js) Example (using node-fetch or axios)
Using node-fetch:
import fetch from 'node-fetch';// Configuration - Replace with your detailsconst tokenEndpoint = 'https://your-domain.auth0.com/oauth/token';const clientId = 'your_auth0_client_id';const clientSecret = 'your_auth0_client_secret';const refreshToken = 'the_refresh_token_you_stored';const requestBody = new URLSearchParams({
grant_type: 'refresh_token',
client_id: clientId,
client_secret: clientSecret,
refresh_token: refreshToken,});fetch(tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: requestBody,})
.then((response) => response.json())
.then((data) => {
if (data.access_token) {
const newAccessToken = data.access_token;
const newRefreshToken = data.refresh_token || refreshToken; // Use new one if provided
console.log('New Access Token:', newAccessToken);
// Store the newRefreshToken if it changed!
} else {
console.error('Error refreshing token:', data);
}
})
.catch((error) => {
console.error('Request failed:', error);
});3. cURL Example (for testing in terminal)
This is very useful for quickly testing if your refresh token is valid.
curl -X POST "https://oauth2.googleapis.com/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "client_id=your_google_client_id" \ -d "client_secret=your_google_client_secret" \ -d "refresh_token=your_refresh_token_here"
Important Security Considerations
Secure Storage: Refresh tokens are long-lived credentials. Never store them on the client side (e.g., in browser localStorage). For web apps, store them in an
HttpOnlycookie. For mobile/desktop apps, use the platform's secure storage (Keychain/Keystore).Client Secret Confidentiality: The
client_secretmust never be exposed on the client side (e.g., in a mobile app or a public SPA). The refresh token flow using a client secret is only safe for confidential clients (like a server-side application).Refresh Token Rotation (Optional but Recommended): Some providers (like Auth0, AWS Cognito) issue a new refresh token every time you use one. The old refresh token becomes invalid. You must always update your stored refresh token if the response contains a new one. If it doesn't, you keep using the original one.
Revocation: If you suspect a refresh token has been compromised, you should revoke it immediately if the provider offers a revocation endpoint.
By following this pattern, you can maintain a seamless user experience without compromising security.
