Refresh Tokens — Long-Lived Credentials for Continuous API Access
In this tutorial, you will learn about Refresh Tokens. We cover key concepts, practical examples, and best practices to help you master this topic.
Refresh tokens are long-lived credentials issued alongside access tokens, allowing clients to obtain new access tokens without requiring the user to re-authenticate.
What You'll Learn
How refresh tokens work, refresh token rotation, secure storage, and implementation for seamless API access across sessions.
Why It Matters
Short-lived access tokens (15-60 minutes) limit damage if stolen. But asking users to log in every hour is impractical. Refresh tokens provide the best of both worlds: frequent token rotation with infrequent user re-authentication.
Real-World Use
Google APIs issue refresh tokens that last until revoked. GitHub tokens can be configured with expiry. DodaTech's Durga Antivirus Pro uses refresh tokens so partner integrations maintain continuous access without user interaction.
flowchart LR
A["Client"] -->|"Login"| B["Auth Server"]
B -->|"Access Token (15 min)\n+ Refresh Token (30 days)"| A
A -->|"API call + Access Token"| C["Resource Server"]
C -->|"Expired"| D["401 Unauthorized"]
D -->|"POST /refresh + Refresh Token"| B
B -->|"New Access Token + New Refresh Token"| A
A -->|"New API call"| C
C -->|"200 OK"| A
style A fill:#dbeafe,stroke:#2563eb
style B fill:#fef3c7,stroke:#d97706
style C fill:#dcfce7,stroke:#16a34a
style D fill:#fecaca,stroke:#dc2626
Access Token vs Refresh Token
| Property | Access Token | Refresh Token |
|---|---|---|
| Lifetime | Short (15-60 min) | Long (days to months) |
| Sent with API calls | Yes | No |
| Can be revoked | Hard (stateless JWT) | Yes (server-side) |
| Contains data | User claims, scopes | Reference or opaque |
| Rotation | Implicit (new one issued) | Optional (rotate on use) |
Code Example: Refresh Token Flow
import requests
import time
TOKEN_URL = "https://auth.example.com/oauth/token"
API_URL = "https://api.example.com/data"
CLIENT_ID = "my-app"
CLIENT_SECRET = "my-secret"
# Simulated stored tokens
tokens = {
"access_token": None,
"refresh_token": None,
"expires_at": 0
}
def get_valid_token():
"""Return a valid access token, refreshing if needed."""
if time.time() < tokens["expires_at"]:
return tokens["access_token"]
if not tokens.get("refresh_token"):
raise Exception("No refresh token available. Re-authenticate.")
# Refresh the access token
response = requests.post(TOKEN_URL, data={
"grant_type": "refresh_token",
"refresh_token": tokens["refresh_token"],
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
})
if response.status_code != 200:
raise Exception("Refresh failed. Re-authenticate.")
new_tokens = response.json()
tokens["access_token"] = new_tokens["access_token"]
tokens["expires_at"] = time.time() + new_tokens["expires_in"]
# Refresh token rotation
if "refresh_token" in new_tokens:
tokens["refresh_token"] = new_tokens["refresh_token"]
return tokens["access_token"]
# First call — exchange code for tokens
code_response = requests.post(TOKEN_URL, data={
"grant_type": "authorization_code",
"code": "auth-code",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
})
initial = code_response.json()
tokens["access_token"] = initial["access_token"]
tokens["refresh_token"] = initial["refresh_token"]
tokens["expires_at"] = time.time() + initial["expires_in"]
# Later API call — auto-refreshes if token expired
access_token = get_valid_token()
response = requests.get(
API_URL,
headers={"Authorization": f"Bearer {access_token}"}
)
print(f"API call status: {response.status_code}")
Refresh Token Rotation
Rotation means each time a refresh token is used, the server returns a new refresh token and invalidates the old one. This prevents a stolen refresh token from being used multiple times.
| Without Rotation | With Rotation |
|---|---|
| Stolen refresh token works forever | Stolen refresh token works once |
| No way to detect token theft | Concurrent use of old and new token alerts the server |
| Revocation requires manual intervention | Automatic single-use limits damage |
Common Mistakes
1. Storing Refresh Tokens in localStorage
XSS can steal refresh tokens. Store them in httpOnly cookies (web) or secure device storage (mobile).
2. Not Using Refresh Token Rotation
Without rotation, a stolen refresh token is valid until it expires. Rotation limits the window of opportunity.
3. Making Refresh Tokens Immortal
Refresh tokens should expire (30-90 days). For sensitive apps, shorter expiry combined with rotation is best.
4. Not Handling Refresh Token Rejection
A 401 on refresh may mean the token is revoked, expired, or already used. Redirect the user to log in again.
5. Sending Refresh Tokens with Every API Call
Refresh tokens are only for the token endpoint. Never send them with regular API calls.
Practice Questions
- What is the purpose of a refresh token?
- Why are access tokens short-lived while refresh tokens are long-lived?
- What is refresh token rotation?
- How should clients detect that a refresh token is invalid?
- What happens when a refresh token expires?
Answers:
- A refresh token obtains new access tokens without requiring the user to re-authenticate.
- Access tokens are sent with every API call — short expiry limits damage if stolen. Refresh tokens are sent rarely and can be revoked server-side.
- Rotation means each refresh request returns a new refresh token and invalidates the old one. A stolen token can only be used once.
- The refresh endpoint returns 401 or invalid_grant. The client should redirect the user to re-authenticate.
- The client receives an error on the refresh attempt. The user must re-authenticate to obtain a new refresh token.
Challenge: Implement a complete refresh token system with rotation, server-side tracking of token lineage, and automatic detection of token theft (when an old, already-used token is presented).
FAQ
Mini Project
Build a token management service with refresh token rotation: an endpoint that issues access + refresh tokens, a refresh endpoint that rotates both, automatic detection of replay attacks, and revocation support.
What's Next
Now compare API Keys vs JWT to understand when each authentication method is appropriate.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro