Refresh Token

Learn how to refresh OAuth2 access tokens to maintain continuous API access.

Overview

OAuth2 access tokens are short-lived for security reasons. When an access token expires, you can use a refresh token to obtain a new access token without requiring the user to re-authenticate. This guide explains how to implement the refresh token flow in your application.

Token Lifecycle

Token Lifecycle Diagram

Token Expiration

When you receive an access token, it includes an expires_in field that indicates the token's lifetime in seconds. Typically, access tokens expire after 1 hour (3600 seconds).

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 3600,
  "refresh_token": "def50200641f3e...",
  "scope": "read:user read:projects"
}

You should track token expiration in your application and proactively refresh tokens before they expire to ensure uninterrupted API access.

Refresh Token Flow

To refresh an access token, make a POST request to the token endpoint with the refresh_token grant type:

POST https://api.iiniit.com/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=REFRESH_TOKEN&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET

The response will contain a new access token and, in some cases, a new refresh token:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 3600,
  "refresh_token": "def50200a9f54e...",
  "scope": "read:user read:projects"
}

Important: Always update your stored tokens when you receive a new refresh token. Refresh tokens may be rotated for security reasons, making previous refresh tokens invalid.

Implementing Token Refresh

When to Refresh Tokens

There are two approaches to refreshing tokens:

  1. Proactive Refresh: Refresh the token before it expires (e.g., when it has 5 minutes of lifetime left).
  2. Reactive Refresh: Refresh the token when an API request fails with a 401 Unauthorized error.

We recommend using a combination of both approaches for the best user experience.

Code Examples

JavaScript Example

// Token management class
class TokenManager {
  constructor(clientId, clientSecret, redirectUri) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.redirectUri = redirectUri;
    this.tokens = this.loadTokens();
  }

  // Load tokens from storage
  loadTokens() {
    const tokensJson = localStorage.getItem('oauth_tokens');
    if (tokensJson) {
      return JSON.parse(tokensJson);
    }
    return null;
  }

  // Save tokens to storage
  saveTokens(tokens) {
    localStorage.setItem('oauth_tokens', JSON.stringify({
      ...tokens,
      created_at: Date.now()
    }));
    this.tokens = tokens;
  }

  // Check if access token is expired or about to expire
  isTokenExpired(bufferSeconds = 300) {
    if (!this.tokens) return true;
    
    const createdAt = this.tokens.created_at;
    const expiresIn = this.tokens.expires_in;
    const expiresAt = createdAt + (expiresIn * 1000);
    const now = Date.now();
    
    // Return true if token is expired or will expire within the buffer time
    return now >= (expiresAt - (bufferSeconds * 1000));
  }

  // Refresh the access token
  async refreshToken() {
    if (!this.tokens?.refresh_token) {
      throw new Error('No refresh token available');
    }

    try {
      const response = await fetch('https://api.iiniit.com/oauth2/token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: new URLSearchParams({
          grant_type: 'refresh_token',
          refresh_token: this.tokens.refresh_token,
          client_id: this.clientId,
          client_secret: this.clientSecret,
        }),
      });

      if (!response.ok) {
        throw new Error('Failed to refresh token');
      }

      const newTokens = await response.json();
      this.saveTokens(newTokens);
      return newTokens;
    } catch (error) {
      console.error('Error refreshing token:', error);
      // Clear tokens if refresh fails
      this.clearTokens();
      throw error;
    }
  }

  // Get a valid access token (refreshing if necessary)
  async getAccessToken() {
    if (this.isTokenExpired()) {
      await this.refreshToken();
    }
    return this.tokens.access_token;
  }

  // Clear tokens (e.g., on logout)
  clearTokens() {
    localStorage.removeItem('oauth_tokens');
    this.tokens = null;
  }
}

// Usage example
const tokenManager = new TokenManager(
  'YOUR_CLIENT_ID',
  'YOUR_CLIENT_SECRET',
  'YOUR_REDIRECT_URI'
);

// API client that handles token refresh
class ApiClient {
  constructor(tokenManager) {
    this.tokenManager = tokenManager;
    this.baseUrl = 'https://api.iiniit.com/v1';
  }

  async request(endpoint, options = {}) {
    try {
      // Get a valid access token
      const accessToken = await this.tokenManager.getAccessToken();
      
      // Set up request with token
      const requestOptions = {
        ...options,
        headers: {
          ...options.headers,
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
        },
      };
      
      // Make the request
      const response = await fetch(`${this.baseUrl}${endpoint}`, requestOptions);
      
      // Handle 401 errors (token might be invalid despite our checks)
      if (response.status === 401) {
        // Try to refresh the token
        await this.tokenManager.refreshToken();
        
        // Retry the request with the new token
        const newAccessToken = await this.tokenManager.getAccessToken();
        requestOptions.headers['Authorization'] = `Bearer ${newAccessToken}`;
        return fetch(`${this.baseUrl}${endpoint}`, requestOptions);
      }
      
      return response;
    } catch (error) {
      console.error('API request error:', error);
      throw error;
    }
  }
  
  // Example API methods
  async getProjects() {
    const response = await this.request('/projects');
    return response.json();
  }
  
  async createProject(data) {
    const response = await this.request('/projects', {
      method: 'POST',
      body: JSON.stringify(data),
    });
    return response.json();
  }
}

// Usage
const apiClient = new ApiClient(tokenManager);

// Example usage
async function fetchProjects() {
  try {
    const projects = await apiClient.getProjects();
    console.log('Projects:', projects);
  } catch (error) {
    console.error('Failed to fetch projects:', error);
    // Handle authentication errors
    if (error.message.includes('token')) {
      // Redirect to login
      window.location.href = '/login';
    }
  }
}

Best Practices

  • Secure Storage: Store refresh tokens securely. For web applications, use HTTP-only cookies or secure storage mechanisms.
  • Token Rotation: Always update your stored tokens when you receive a new refresh token.
  • Error Handling: Implement proper error handling for token refresh failures.
  • Proactive Refresh: Refresh tokens before they expire to prevent disruption to the user experience.
  • Offline Access: If your application needs offline access, request the appropriate scope during authorization.

Refresh Token Expiration

Refresh tokens have a longer lifetime than access tokens, but they can still expire. The iiniit API refresh tokens typically expire after 30 days of inactivity. If a refresh token expires, the user will need to re-authenticate.

Troubleshooting

Error Cause Solution
invalid_grant The refresh token is invalid or expired Redirect the user to re-authenticate
invalid_client Client authentication failed Check your client ID and client secret
invalid_request Missing required parameter Check that all required parameters are included
unauthorized_client Client is not authorized to use refresh token grant Check your OAuth2 application settings

Next Steps

Now that you understand how to refresh tokens, you can: