Token Management
Learn how to handle JWT token expiration and refresh tokens.
Token Lifetimes
| Token | Lifetime | Purpose |
|---|---|---|
| Access Token | 60 minutes | Make API requests |
| Refresh Token | 1 day | Get new access token when expired |
When Access Token Expires
After 60 minutes, API requests will return 401 Unauthorized:
Solution: Use the refresh token to get a new access token.
Refreshing Access Token
Request
Response
JavaScript Implementation
async function refreshAccessToken() {
const refreshToken = window.authTokens.refresh;
try {
const response = await fetch('http://localhost:8000/api/token/refresh/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
refresh: refreshToken
})
});
if (response.ok) {
const data = await response.json();
// Update access token
window.authTokens.access = data.access;
console.log('Token refreshed successfully');
return true;
} else {
console.error('Token refresh failed');
return false;
}
} catch (error) {
console.error('Error refreshing token:', error);
return false;
}
}
Automatic Token Refresh
Handle 401 errors automatically and retry the request:
async function makeAPIRequest(endpoint, options = {}, retry = true) {
const accessToken = window.authTokens.access;
const response = await fetch(`http://localhost:8000${endpoint}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
}
});
// If 401 and we haven't retried yet, refresh token
if (response.status === 401 && retry) {
console.log('Token expired, refreshing...');
const refreshed = await refreshAccessToken();
if (refreshed) {
// Retry original request with new token
return makeAPIRequest(endpoint, options, false);
} else {
// Refresh failed - user needs to re-authenticate
console.log('Session expired. Please sign in again.');
redirectToLogin();
}
}
return response;
}
Complete Example (React with All Auth Methods)
import { useState, useEffect } from 'react';
function App() {
const [tokens, setTokens] = useState({ access: null, refresh: null });
const [user, setUser] = useState(null);
// Generic authenticate function (works with all methods)
const authenticate = async (endpoint, authData) => {
const response = await fetch(`http://localhost:8000${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(authData)
});
if (response.ok) {
const data = await response.json();
setTokens({ access: data.access, refresh: data.refresh });
setUser(data.user);
return true;
}
return false;
};
// Email/Password Signup
const handleSignup = async (email, password, firstName, lastName) => {
return authenticate('/api/signup/', {
email,
password,
password_confirm: password,
first_name: firstName,
last_name: lastName
});
};
// Email/Password Login
const handleEmailLogin = async (email, password) => {
return authenticate('/api/login/', { email, password });
};
// Google OAuth
const handleGoogleSignIn = async (googleToken) => {
return authenticate('/api/auth/google/', { access_token: googleToken });
};
// Facebook OAuth
const handleFacebookSignIn = async (facebookToken) => {
return authenticate('/api/auth/facebook/', { access_token: facebookToken });
};
// Microsoft OAuth
const handleMicrosoftSignIn = async (microsoftToken) => {
return authenticate('/api/auth/microsoft/', { access_token: microsoftToken });
};
// Refresh access token
const refreshToken = async () => {
const response = await fetch('http://localhost:8000/api/token/refresh/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh: tokens.refresh })
});
if (response.ok) {
const data = await response.json();
setTokens(prev => ({ ...prev, access: data.access }));
return true;
}
return false;
};
// Make API request with automatic refresh
const apiRequest = async (endpoint, options = {}) => {
let response = await fetch(`http://localhost:8000${endpoint}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${tokens.access}`,
'Content-Type': 'application/json',
}
});
// Handle 401 - token expired
if (response.status === 401) {
const refreshed = await refreshToken();
if (refreshed) {
// Retry with new token
response = await fetch(`http://localhost:8000${endpoint}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${tokens.access}`,
'Content-Type': 'application/json',
}
});
}
}
return response;
};
// Logout
const handleLogout = () => {
setTokens({ access: null, refresh: null });
setUser(null);
console.log('Logged out successfully');
};
// Example: Fetch user profile
useEffect(() => {
if (tokens.access) {
apiRequest('/api/me/')
.then(res => res.json())
.then(data => console.log('Profile:', data));
}
}, [tokens.access]);
return (
<div>
{user ? (
<div>
<h1>Welcome, {user.first_name}!</h1>
<p>Email: {user.email}</p>
<button onClick={handleLogout}>Logout</button>
</div>
) : (
<div>
<button onClick={() => handleGoogleSignIn('GOOGLE_TOKEN')}>
Sign in with Google
</button>
<button onClick={() => handleFacebookSignIn('FACEBOOK_TOKEN')}>
Sign in with Facebook
</button>
<button onClick={() => handleMicrosoftSignIn('MICROSOFT_TOKEN')}>
Sign in with Microsoft
</button>
<button onClick={() => handleEmailLogin('[email protected]', 'password')}>
Sign in with Email
</button>
</div>
)}
</div>
);
}
When Refresh Token Expires
After 1 day, the refresh token expires. When this happens:
- Refresh attempt will return
401 Unauthorized - User must sign in again with Google OAuth
- Clear all stored tokens
function handleRefreshFailure() {
// Clear tokens
window.authTokens = { access: null, refresh: null };
// Show login screen
alert('Your session has expired. Please sign in again.');
// Redirect to login
window.location.href = '/login';
}
Testing Token Refresh
With cURL
# 1. Get tokens from authentication
ACCESS_TOKEN="your_access_token"
REFRESH_TOKEN="your_refresh_token"
# 2. Make API request (works)
curl http://localhost:8000/api/me/ \
-H "Authorization: Bearer $ACCESS_TOKEN"
# 3. Wait 60+ minutes (or manually expire token)
# 4. API request fails with 401
curl http://localhost:8000/api/me/ \
-H "Authorization: Bearer $ACCESS_TOKEN"
# 5. Refresh the token
NEW_ACCESS_TOKEN=$(curl -X POST http://localhost:8000/api/token/refresh/ \
-H "Content-Type: application/json" \
-d "{\"refresh\":\"$REFRESH_TOKEN\"}" \
| jq -r '.access')
# 6. Use new access token (works)
curl http://localhost:8000/api/me/ \
-H "Authorization: Bearer $NEW_ACCESS_TOKEN"
Best Practices
✅ DO:
- Refresh tokens automatically on 401 errors
- Store refresh token securely
- Clear tokens on logout
- Handle refresh failures gracefully
- Show "session expired" message to users
❌ DON'T:
- Ignore 401 errors
- Try to refresh an expired refresh token
- Store tokens in localStorage
- Refresh on every request (only when needed)
Error Codes
| Status | Error Code | Meaning | Solution |
|---|---|---|---|
| 401 | token_not_valid |
Access token expired | Refresh token |
| 401 | token_not_valid |
Refresh token expired | Re-authenticate with Google |
| 400 | invalid_request |
Missing or malformed request | Check request format |
Summary
- Access tokens expire after 60 minutes → Use refresh token to get new one
- Refresh tokens expire after 1 day → User must sign in again with Google
- Handle 401 errors → Automatically refresh and retry
- Clear tokens on refresh failure → Prompt user to sign in again
Next Steps
- API Reference - Complete endpoint documentation
- Authentication Setup - Initial authentication guide
- Interactive API Explorer - Try the API live