fetch APIにBearer Tokenを自動注入する認証ヘルパーの作り方

問題

API呼び出しのたびにheaders: { 'Authorization': 'Bearer ...' }を繰り返し追加していました。トークンの更新やエラー処理もAPI毎に別々に行っていました。

解決方法

let authToken: string | null = null;

export function setAuthToken(token: string | null) {
  authToken = token;
  if (token) localStorage.setItem('auth_token', token);
  else localStorage.removeItem('auth_token');
}

function getAuthToken(): string | null {
  if (!authToken) authToken = localStorage.getItem('auth_token');
  return authToken;
}

async function fetchWithAuth<T>(
  endpoint: string,
  options: RequestInit = {}
): Promise<T> {
  const token = getAuthToken();
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    ...(options.headers as Record<string, string>),
  };
  if (token) headers['Authorization'] = `Bearer ${token}`;

  const res = await fetch(`${API_URL}${endpoint}`, { ...options, headers });

  if (!res.ok) {
    const err = await res.json().catch(() => ({ message: 'Unknown error' }));
    throw new Error(err.message || `HTTP ${res.status}`);
  }

  return res.json();
}

ポイント

  • authToken変数とlocalStorageの二重管理をしています。まずメモリから探し、なければlocalStorageを確認します。毎回localStorage.getItemを呼ぶより高速です。
  • すべてのAPI関数はfetchWithAuth<ResponseType>('/endpoint')の1行で呼び出せます。トークン管理ロジックが1箇所にまとまっているため、リフレッシュロジックの追加も簡単です。
  • res.json().catch()でJSONパース失敗も処理します。サーバーがHTMLエラーページを返した場合にクラッシュしません。