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

Auth Mode

The shape of the generated auth interceptor is controlled by auth_mode in [instance]. Pick the one that matches how your app stores credentials:

auth_modeGenerated interceptor
bearer-localstorageSPA pattern — reads a token from localStorage[token_key] and attaches Authorization: Bearer <token>
custom (default)A pass-through interceptor with a TODO comment for you to implement
noneNo interceptor is attached
api.config.toml
[instance]
auth_mode = "bearer-localstorage"
token_key = "auth-token"   # only used by bearer-localstorage

You can set this during init:

chowbea-axios init --auth-mode bearer-localstorage --token-key auth-token

api.instance.ts is generated once and then never overwritten, so switching modes after init means either editing the file directly or re-running init --force (after backing up your changes).

bearer-localstorage — what's actually generated

When auth_mode = "bearer-localstorage", the generated interceptor parses the localStorage value flexibly:

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

This handles:

  • Plain token strings
  • JSON objects with a token property
  • Zustand-persist–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 fetch (e.g. a private staging endpoint), use the [fetch.auth] block. Today only HTTP Basic Auth is supported:

api.config.toml
[fetch.auth]
type = "basic"
username = "$SWAGGER_USER"
password = "$SWAGGER_PASS"

$VAR and ${VAR} are both interpolated from the environment. If a referenced variable is unset, the CLI prompts interactively (in a TTY) or fails with a clear error (in CI).

Set the variables before running:

export SWAGGER_USER="ci-bot"
export SWAGGER_PASS="..."
chowbea-axios fetch

CI/CD Setup

In GitHub Actions:

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

chowbea-axios init can scaffold a hardened workflow for you at .github/workflows/chowbea-axios-ci.yml — default-deny permissions, concurrency cancel-in-progress, pinned action SHAs, and a vars/secrets fallback for the spec endpoint. Pass --skip-workflow to opt out.

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