Prevent Axios Credential Leakage Across Routes

Managing authentication credentials across mixed public and private API endpoints introduces the risk of leaking sensitive tokens to unauthorized third parties or unauthenticated routes. When using Axios, credential leakage typically occurs due to globally shared default headers, improper request interceptor logic, or sending authenticated requests to external URLs. Implementing isolated client instances, route-specific interceptors, strict URL origin validation, and secure token storage prevents credentials from being exposed across application boundaries.

Separate Public and Authenticated Instances

The most effective pattern to avoid token leakage is separating the HTTP client into dedicated instances rather than relying on the global axios object or a single shared instance.

import axios from 'axios';

// Public instance: No auth headers attached
export const publicApi = axios.create({
  baseURL: 'https://api.example.com/v1',
  timeout: 10000,
});

// Authenticated instance: Configured for protected routes
export const privateApi = axios.create({
  baseURL: 'https://api.example.com/v1',
  timeout: 10000,
});

By decoupling these instances at the module level, developers eliminate the possibility of accidentally inheriting global Authorization headers on public calls.

Domain and Origin Whitelisting in Interceptors

When using interceptors on private instances, enforce strict domain validation to ensure tokens are only appended to requests heading to trusted first-party servers. This prevents scenarios where an authenticated client makes a call to a relative path that redirects to an external service (such as an S3 bucket or third-party webhook) while carrying the Authorization header.

privateApi.interceptors.request.use((config) => {
  const allowedOrigin = 'https://api.example.com';
  const targetUrl = new URL(config.url, config.baseURL);

  if (targetUrl.origin === allowedOrigin) {
    const token = getAuthToken();
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
  } else {
    // Strip or prevent adding the Authorization header for mismatched origins
    delete config.headers.Authorization;
  }

  return config;
});

Request-Level Metadata Flags

In architectures where a single instance is mandatory, use custom Axios request configuration flags to explicitly mark whether a request requires authentication.

// Request definition
export const fetchPublicContent = () => {
  return apiClient.get('/content/public', {
    requiresAuth: false,
  });
};

// Interceptor evaluation
apiClient.interceptors.request.use((config) => {
  if (config.requiresAuth !== false) {
    const token = getAuthToken();
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
  }
  return config;
});

Explicitly checking metadata prevents the default assumption that every outgoing request should include credentials.

Isolation of External and Third-Party Calls

File uploads, analytics pings, and payment gateway interactions often require direct communication with third-party domains. Never reuse internal Axios instances for external endpoints. Instead, use an isolated, minimal instance:

export const externalUploadClient = axios.create({
  // Clean configuration free from custom interceptors or default auth headers
  timeout: 30000,
});

Secure Credential Transport with HttpOnly Cookies

Whenever feasible, avoid manual header injection in JavaScript altogether by relying on HttpOnly, Secure, and SameSite cookies for authentication. Setting withCredentials: true on the private Axios instance allows the browser to handle token transmission automatically. When combined with strict CORS policies and SameSite=Lax or SameSite=Strict, the browser ensures credentials are never accessible via JavaScript and are only sent to the matching origin domain.