chowbea-axios
Advanced

Authentication

Configure authentication for API requests and protected specs.

chowbea-axios supports multiple authentication patterns for both your API requests and protected OpenAPI specs.

API Request Authentication

Default: localStorage Token

By default, the generated Axios instance reads a token from localStorage:

api.instance.ts (generated)
axiosInstance.interceptors.request.use((config) => {
  if (typeof window !== "undefined") {
    const tokenObject = localStorage.getItem("auth-token");
    if (tokenObject) {
      try {
        const parsed = JSON.parse(tokenObject);
        const token = parsed.state?.token || parsed.token || parsed;
        if (typeof token === "string") {
          config.headers.Authorization = `Bearer ${token}`;
        }
      } catch {
        config.headers.Authorization = `Bearer ${tokenObject}`;
      }
    }
  }
  return config;
});

This handles:

  • Plain token strings
  • JSON objects with token property
  • Zustand-style { state: { token } } objects

Custom Token Key

Change the localStorage key during init:

chowbea-axios init --token-key "session-token"

Or in your config:

api.config.toml
[instance]
token_key = "session-token"

Custom Token Retrieval

Modify api.instance.ts for custom logic:

api.instance.ts
import { axiosInstance } from "./api.instance";

// Example: Read from sessionStorage
axiosInstance.interceptors.request.use((config) => {
  const session = sessionStorage.getItem("session");
  if (session) {
    const { accessToken } = JSON.parse(session);
    config.headers.Authorization = `Bearer ${accessToken}`;
  }
  return config;
});

// Example: Read from a state management store
import { useAuthStore } from "../stores/auth";

axiosInstance.interceptors.request.use((config) => {
  const token = useAuthStore.getState().token;
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Example: Read from cookies (server-side)
axiosInstance.interceptors.request.use((config) => {
  if (typeof document !== "undefined") {
    const match = document.cookie.match(/token=([^;]+)/);
    if (match) {
      config.headers.Authorization = `Bearer ${match[1]}`;
    }
  }
  return config;
});

Enable credentials for cookie-based auth:

api.config.toml
[instance]
with_credentials = true

This sets withCredentials: true on the Axios instance, sending cookies with cross-origin requests.

API Key Authentication

For API key auth, modify the interceptor:

api.instance.ts
axiosInstance.interceptors.request.use((config) => {
  config.headers["X-API-Key"] = import.meta.env.VITE_API_KEY;
  return config;
});

Multiple Auth Methods

Support multiple authentication methods:

api.instance.ts
axiosInstance.interceptors.request.use((config) => {
  // Priority 1: Bearer token
  const token = localStorage.getItem("auth-token");
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
    return config;
  }
  
  // Priority 2: API Key
  const apiKey = localStorage.getItem("api-key");
  if (apiKey) {
    config.headers["X-API-Key"] = apiKey;
    return config;
  }
  
  // No auth
  return config;
});

Token Refresh

Handle expired tokens with a response interceptor:

api.instance.ts
let isRefreshing = false;
let refreshSubscribers: ((token: string) => void)[] = [];

function subscribeTokenRefresh(cb: (token: string) => void) {
  refreshSubscribers.push(cb);
}

function onRefreshed(token: string) {
  refreshSubscribers.forEach(cb => cb(token));
  refreshSubscribers = [];
}

axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    
    if (error.response?.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        // Wait for refresh to complete
        return new Promise((resolve) => {
          subscribeTokenRefresh((token) => {
            originalRequest.headers.Authorization = `Bearer ${token}`;
            resolve(axiosInstance(originalRequest));
          });
        });
      }
      
      originalRequest._retry = true;
      isRefreshing = true;
      
      try {
        const { data } = await axios.post("/auth/refresh", {
          refreshToken: localStorage.getItem("refresh-token"),
        });
        
        localStorage.setItem("auth-token", data.accessToken);
        onRefreshed(data.accessToken);
        
        originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
        return axiosInstance(originalRequest);
      } catch (refreshError) {
        // Refresh failed, redirect to login
        window.location.href = "/login";
        return Promise.reject(refreshError);
      } finally {
        isRefreshing = false;
      }
    }
    
    return Promise.reject(error);
  }
);

Protected OpenAPI Specs

For specs that require authentication to access:

Static Token

api.config.toml
[fetch]
headers = { Authorization = "Bearer your-static-token" }

Environment Variable

api.config.toml
[fetch]
headers = { Authorization = "Bearer $API_TOKEN" }

Then set the variable before running:

export API_TOKEN="your-secret-token"
chowbea-axios fetch

Multiple Headers

api.config.toml
[fetch]
headers = { 
  Authorization = "Bearer $API_TOKEN",
  X-API-Key = "$API_KEY"
}

CI/CD Setup

In your CI pipeline:

.github/workflows/build.yml
jobs:
  build:
    steps:
      - name: Generate API types
        env:
          API_TOKEN: ${{ secrets.API_TOKEN }}
        run: chowbea-axios fetch

Handling 401 Errors

The generated error handling includes an UNAUTHORIZED code:

const { data, error } = await api.get("/protected-resource");

if (error) {
  if (error.code === "UNAUTHORIZED") {
    // Token expired or invalid
    localStorage.removeItem("auth-token");
    window.location.href = "/login";
    return;
  }
}

Security Best Practices

  1. Never commit tokens - Use environment variables
  2. Use HTTPS - Always in production
  3. Set appropriate CORS - Only allow your domains
  4. Implement token refresh - Don't use long-lived tokens
  5. Clear tokens on logout - Clean up localStorage/cookies

Next Steps

On this page