Skip to main content

Overview

The Blog Marketing Platform uses JWT (JSON Web Tokens) for authentication. This page covers token management, including refreshing expired tokens and validating token status.
All tokens are JWT format and contain user information in the payload. Access tokens expire after 24 hours, while refresh tokens have a longer lifespan.

Token Types

Access Token

Purpose: Used to authenticate API requests Lifespan: 24 hours from issuance Usage: Include in the Authorization header as a Bearer token:
Authorization: Bearer <accessToken>
Structure:
{
  "id": 123,
  "email": "[email protected]",
  "role": "autor",
  "exp": 1678901234000
}

Refresh Token

Purpose: Used to obtain a new access token when the current one expires Lifespan: Extended period (implementation-dependent) Usage: Send to the refresh endpoint when access token expires Storage: Store securely in httpOnly cookies or secure storage (never in localStorage for production)
Refresh tokens are sensitive credentials. Never expose them in logs, URLs, or client-side JavaScript. Use httpOnly cookies in production environments.

Refresh Token Endpoint

Overview

Obtain a new access token using a valid refresh token. This endpoint should be called when the access token expires or is about to expire.

Request

Body Parameters

refreshToken
string
required
Valid refresh token received during login or registration.Format: JWT token string

Response

accessToken
string
New JWT access token valid for 24 hours
refreshToken
string
New refresh token (optional - may return the same or a new refresh token depending on implementation)
user
object
Updated user information (optional - may include latest user data)

Code Examples

interface RefreshTokenRequest {
  refreshToken: string;
}

interface RefreshTokenResponse {
  accessToken: string;
  refreshToken?: string;
  user?: User;
}

async function refreshAccessToken(): Promise<string> {
  const refreshToken = localStorage.getItem('refresh_token');
  
  if (!refreshToken) {
    throw new Error('No refresh token available');
  }
  
  const response = await fetch('https://api.example.com/api/v1/auths/refresh', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ refreshToken }),
  });
  
  if (!response.ok) {
    // Refresh token is invalid or expired, user needs to log in again
    localStorage.removeItem('access_token');
    localStorage.removeItem('refresh_token');
    throw new Error('Refresh token expired');
  }
  
  const data = await response.json();
  
  // Update stored tokens
  localStorage.setItem('access_token', data.accessToken);
  
  if (data.refreshToken) {
    localStorage.setItem('refresh_token', data.refreshToken);
  }
  
  if (data.user) {
    localStorage.setItem('user_data', JSON.stringify(data.user));
  }
  
  return data.accessToken;
}

// Automatic token refresh with retry logic
async function apiCallWithAutoRefresh(url: string, options: RequestInit = {}) {
  const accessToken = localStorage.getItem('access_token');
  
  const headers = {
    ...options.headers,
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  };
  
  let response = await fetch(url, { ...options, headers });
  
  // If token expired (401), refresh and retry
  if (response.status === 401) {
    console.log('Access token expired, refreshing...');
    const newAccessToken = await refreshAccessToken();
    
    // Retry request with new token
    headers.Authorization = `Bearer ${newAccessToken}`;
    response = await fetch(url, { ...options, headers });
  }
  
  return response;
}

// Usage
const response = await apiCallWithAutoRefresh('https://api.example.com/api/v1/posts');
const posts = await response.json();

Error Responses

401
error
Unauthorized - Invalid or expired refresh token
{
  "statusCode": 401,
  "message": "Invalid refresh token"
}
Action: User must log in again
400
error
Bad Request - Missing refresh token
{
  "statusCode": 400,
  "message": "Refresh token is required"
}

Token Validation

Client-Side Validation

You can decode and validate JWT tokens on the client side to check expiration without making an API call.
interface TokenPayload {
  id: number;
  email: string;
  role: string;
  exp: number; // Unix timestamp in seconds (for real JWT) or milliseconds (for mock)
}

function isTokenExpired(token: string): boolean {
  if (!token) return true;
  
  try {
    const isMockToken = !token.includes('.');
    let payload: TokenPayload;
    
    if (isMockToken) {
      // Mock token (base64 encoded JSON)
      payload = JSON.parse(atob(token));
      return payload.exp < Date.now();
    } else {
      // Real JWT token (format: header.payload.signature)
      const parts = token.split('.');
      if (parts.length !== 3) return true;
      
      // Decode payload (base64url)
      const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
      payload = JSON.parse(atob(base64));
      
      // JWT exp is in seconds, convert to milliseconds
      return payload.exp * 1000 < Date.now();
    }
  } catch (error) {
    console.error('Error validating token:', error);
    return true;
  }
}

function getTokenPayload(token: string): TokenPayload | null {
  if (!token) return null;
  
  try {
    const isMockToken = !token.includes('.');
    
    if (isMockToken) {
      return JSON.parse(atob(token));
    } else {
      const parts = token.split('.');
      if (parts.length !== 3) return null;
      
      const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
      return JSON.parse(atob(base64));
    }
  } catch (error) {
    console.error('Error decoding token:', error);
    return null;
  }
}

function shouldRefreshToken(token: string): boolean {
  const payload = getTokenPayload(token);
  if (!payload) return true;
  
  const isMockToken = !token.includes('.');
  const expirationTime = isMockToken ? payload.exp : payload.exp * 1000;
  const timeUntilExpiry = expirationTime - Date.now();
  
  // Refresh if less than 5 minutes until expiry
  return timeUntilExpiry < 5 * 60 * 1000;
}

// Usage
const token = localStorage.getItem('access_token');

if (isTokenExpired(token)) {
  console.log('Token expired, need to refresh');
  await refreshAccessToken();
} else if (shouldRefreshToken(token)) {
  console.log('Token expiring soon, refreshing proactively');
  await refreshAccessToken();
}
Client-side token validation only checks the expiration time and structure. It does NOT verify the token signature. Server-side validation is still required for security.

Token Refresh Strategies

Strategy 1: Reactive Refresh (On 401)

Refresh the token only when an API call returns 401 Unauthorized. Pros: Simple implementation, no unnecessary refresh calls Cons: User may experience a brief delay on the first expired request
// Implemented in "Automatic token refresh with retry logic" example above

Strategy 2: Proactive Refresh (Before Expiry)

Refresh the token before it expires (e.g., 5 minutes before expiration). Pros: Seamless user experience, no API call failures Cons: Requires periodic checks, may refresh unnecessarily
function setupTokenRefreshTimer() {
  setInterval(async () => {
    const token = localStorage.getItem('access_token');
    
    if (shouldRefreshToken(token)) {
      try {
        await refreshAccessToken();
        console.log('Token refreshed proactively');
      } catch (error) {
        console.error('Failed to refresh token:', error);
      }
    }
  }, 60000); // Check every minute
}

// Start timer on app initialization
setupTokenRefreshTimer();
Combine both strategies: proactive refresh with 401 fallback.
// Use proactive refresh timer
setupTokenRefreshTimer();

// AND use automatic refresh on 401
apiCallWithAutoRefresh(url, options);

Best Practices

Follow these best practices for secure token management:

Storage

  • Development: localStorage is acceptable for development
  • Production: Use httpOnly cookies or secure storage mechanisms
  • Never: Store tokens in session storage, URL parameters, or local variables that persist

Security

  1. Always use HTTPS in production to prevent token interception
  2. Implement token rotation: Issue new refresh tokens periodically
  3. Set appropriate token lifespans: Short for access tokens (minutes to hours), longer for refresh tokens (days to weeks)
  4. Implement logout: Clear all tokens and invalidate on the server
  5. Monitor for suspicious activity: Track token usage patterns

Error Handling

try {
  await refreshAccessToken();
} catch (error) {
  // Refresh failed - clear tokens and redirect to login
  localStorage.removeItem('access_token');
  localStorage.removeItem('refresh_token');
  localStorage.removeItem('user_data');
  
  window.location.href = '/login';
}

Concurrent Requests

When multiple API calls are made simultaneously and the token expires, prevent multiple refresh requests:
let refreshPromise: Promise<string> | null = null;

async function getValidAccessToken(): Promise<string> {
  const token = localStorage.getItem('access_token');
  
  if (!isTokenExpired(token)) {
    return token!;
  }
  
  // If already refreshing, wait for that promise
  if (refreshPromise) {
    return refreshPromise;
  }
  
  // Start new refresh
  refreshPromise = refreshAccessToken()
    .finally(() => {
      refreshPromise = null;
    });
  
  return refreshPromise;
}

Token Lifecycle Diagram


  • Login - Obtain initial tokens
  • Register - Create account and obtain tokens

Build docs developers (and LLMs) love