OS Trading Engine
API Reference
Authentication
Token Refresh

Token Refresh

When access tokens expire, use the refresh token to obtain new tokens without re-authenticating.


Endpoint

POST /api/v1/auth/refresh

Authentication: None required (refresh token in body)


Request

Headers

Content-Type: application/json

Body

{
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
FieldTypeRequiredDescription
refreshTokenstringYesValid refresh token from login

Response

Success (200)

{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
⚠️

Important: A new refresh token is returned. The old refresh token is invalidated and cannot be reused. Store the new refresh token for future refreshes.

Error Responses

Missing Token (400)

{
  "error": "Refresh token is required"
}

Invalid/Expired Token (401)

{
  "error": "Invalid or expired refresh token"
}

Wrong Token Type (401)

{
  "error": "Invalid token type"
}

Token Already Used (401)

{
  "error": "Refresh token has already been used or revoked"
}

User Not Found (401)

{
  "error": "User not found"
}

Example

cURL

curl -X POST https://your-instance.com/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'

JavaScript

async function refreshTokens(refreshToken) {
  const response = await fetch('/api/v1/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refreshToken }),
  });
 
  if (!response.ok) {
    // Refresh failed - redirect to login
    throw new Error('Session expired');
  }
 
  const { accessToken, refreshToken: newRefreshToken } = await response.json();
  
  // Store BOTH new tokens
  storeAccessToken(accessToken);
  storeRefreshToken(newRefreshToken);  // Important: use the NEW token
  
  return accessToken;
}

Token Rotation

Nexgent implements refresh token rotation for security:

1. Client sends refresh token
   └─► Token is consumed (deleted from Redis)

2. Server validates token
   └─► Checks signature, expiry, and Redis record

3. Server issues NEW token pair
   └─► New access token (15 min)
   └─► New refresh token (24h)

4. Old refresh token is invalidated
   └─► Cannot be reused
   └─► Reuse attempts are logged as potential theft

Why Rotation?

Without RotationWith Rotation
Stolen token works until expiryStolen token works only once
Attacker has 24h windowLegitimate use invalidates stolen token
No theft detectionReuse attempts detected

Handling Refresh in Your App

Proactive Refresh

Refresh before the access token expires:

// Refresh at 14 minutes (1 minute before 15-minute expiry)
const REFRESH_THRESHOLD_MS = 14 * 60 * 1000;
 
function scheduleRefresh(accessToken) {
  const payload = parseJwt(accessToken);
  const expiresAt = payload.exp * 1000;
  const refreshAt = expiresAt - REFRESH_THRESHOLD_MS;
  const delay = refreshAt - Date.now();
 
  if (delay > 0) {
    setTimeout(() => refreshTokens(), delay);
  }
}

Reactive Refresh (on 401)

Retry failed requests after refreshing:

async function apiRequest(url, options) {
  let response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${getAccessToken()}`,
    },
  });
 
  if (response.status === 401) {
    // Try to refresh
    try {
      await refreshTokens(getRefreshToken());
      
      // Retry original request with new token
      response = await fetch(url, {
        ...options,
        headers: {
          ...options.headers,
          'Authorization': `Bearer ${getAccessToken()}`,
        },
      });
    } catch {
      // Refresh failed - session expired
      redirectToLogin();
      throw new Error('Session expired');
    }
  }
 
  return response;
}

Common Issues

"Refresh token has already been used"

Cause: The same refresh token was used twice.

Solutions:

  1. Ensure you store the new refresh token from each refresh response
  2. Don't make concurrent refresh requests
  3. Use a mutex/lock for refresh operations

"Invalid or expired refresh token"

Cause: Token is malformed, expired, or was revoked.

Solutions:

  1. Check if user logged out (token was revoked)
  2. Check if token expired (24h default, 30 days with "Remember Me")
  3. Redirect to login for re-authentication

Race Conditions

Multiple tabs/requests refreshing simultaneously:

let refreshPromise = null;
 
async function refreshTokens() {
  // Reuse existing refresh if in progress
  if (refreshPromise) {
    return refreshPromise;
  }
 
  refreshPromise = doRefresh();
  
  try {
    return await refreshPromise;
  } finally {
    refreshPromise = null;
  }
}