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_mode | Generated interceptor |
|---|---|
bearer-localstorage | SPA 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 |
none | No interceptor is attached |
[instance]
auth_mode = "bearer-localstorage"
token_key = "auth-token" # only used by bearer-localstorageYou can set this during init:
chowbea-axios init --auth-mode bearer-localstorage --token-key auth-tokenapi.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:
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
tokenproperty - 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:
[instance]
token_key = "session-token"Custom Token Retrieval
Modify api.instance.ts for custom logic:
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;
});Cookie-Based Authentication
Enable credentials for cookie-based auth:
[instance]
with_credentials = trueThis sets withCredentials: true on the Axios instance, sending cookies with cross-origin requests.
API Key Authentication
For API key auth, modify the interceptor:
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:
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:
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:
[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 fetchCI/CD Setup
In GitHub Actions:
jobs:
build:
steps:
- name: Generate API types
env:
SWAGGER_USER: ${{ secrets.SWAGGER_USER }}
SWAGGER_PASS: ${{ secrets.SWAGGER_PASS }}
run: npx chowbea-axios fetchchowbea-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
- Never commit tokens - Use environment variables
- Use HTTPS - Always in production
- Set appropriate CORS - Only allow your domains
- Implement token refresh - Don't use long-lived tokens
- Clear tokens on logout - Clean up localStorage/cookies