POST /api/token/refresh
Obtains a new access token using the refresh token.
Endpoint
POST /api/token/refresh
Headers
| Header | Value | Required |
|---|---|---|
| Content-Type | application/json | Yes |
Request Body
{
"refresh_token": "string"
}
| Field | Type | Required | Description |
|---|---|---|---|
| refresh_token | string | Yes | Refresh token obtained during login |
Success Response
Code: 200 OK
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3MDUzMTU5NDUsImV4cCI6MTcwNTMxOTU0NSwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJlbWFpbCI6ImFkbWluQHBjaC1zaWcuc24ifQ...",
"refresh_token": "e2f3a4b5c6d7e8f9g0h1i2j3k4l5m6n7o8p9q0r1"
}
| Field | Type | Description |
|---|---|---|
| token | string | New JWT access token |
| refresh_token | string | New refresh token (rotation) |
Error Responses
401 Unauthorized - Invalid refresh token
{
"code": 401,
"message": "Invalid refresh token"
}
401 Unauthorized - Expired refresh token
{
"code": 401,
"message": "Refresh token has expired"
}
Examples
cURL
curl -X POST https://sig.ucp-pch.org/api/token/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "d1f2a3b4c5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0"
}'
JavaScript
const refreshAccessToken = async () => {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await fetch('https://sig.ucp-pch.org/api/token/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
refresh_token: refreshToken
})
});
if (!response.ok) {
// Invalid refresh token, redirect to login
localStorage.removeItem('token');
localStorage.removeItem('refresh_token');
window.location.href = '/login';
throw new Error('Session expired');
}
const data = await response.json();
// Update tokens
localStorage.setItem('token', data.token);
localStorage.setItem('refresh_token', data.refresh_token);
return data.token;
};
JavaScript - Axios Interceptor
import axios from 'axios';
const api = axios.create({
baseURL: 'https://sig.ucp-pch.org/api'
});
// Interceptor to automatically refresh the token
api.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const newToken = await refreshAccessToken();
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
return api(originalRequest);
} catch (refreshError) {
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
Python
import requests
def refresh_token(current_refresh_token):
response = requests.post(
'https://sig.ucp-pch.org/api/token/refresh',
json={
'refresh_token': current_refresh_token
}
)
if response.status_code == 200:
data = response.json()
return {
'token': data['token'],
'refresh_token': data['refresh_token']
}
else:
raise Exception('Failed to refresh token')
# Usage
try:
tokens = refresh_token('d1f2a3b4c5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0')
print(f"New token: {tokens['token'][:50]}...")
except Exception as e:
print(f"Error: {e}")
Token Rotation
The system uses refresh token rotation:
- Each call to
/api/token/refreshgenerates a new refresh token - The old refresh token is invalidated
- This limits the risk in case of refresh token theft
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │ │ Backend │ │ Redis │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ refresh_token_A │ │
│──────────────────▶│ │
│ │ Verify token_A │
│ │──────────────────▶│
│ │ OK │
│ │◀──────────────────│
│ │ Invalidate token_A│
│ │──────────────────▶│
│ │ Create token_B │
│ │──────────────────▶│
│ token + token_B │ │
│◀──────────────────│ │
Best Practices
When to refresh?
- Proactively: A few minutes before token expiration
- Reactively: After receiving a 401 error
Calculating refresh timing
const shouldRefreshToken = () => {
const token = localStorage.getItem('token');
if (!token) return false;
try {
const payload = JSON.parse(atob(token.split('.')[1]));
const expiresAt = payload.exp * 1000;
const now = Date.now();
// Refresh 5 minutes before expiration
return expiresAt - now < 5 * 60 * 1000;
} catch {
return false;
}
};
Notes
- The refresh token expires after 7 days
- Store the refresh token securely (httpOnly cookie if possible)
- In case of refresh error, redirect the user to the login page