Swoff Swoff

Auth-Aware Fetch

Extend fetchWithCache to attach auth credentials and handle 401 responses.

Auth-Aware Fetch

Prerequisites: Auth Store set up, API Integration fetchWithCache in place.

How the CLI Generates This

Set features.auth.type in swoff.config.json:

type valuewithAuthHeaders() behaviorauthenticatedFetch needed?
"cookie"No-op — browser auto-sends httpOnly cookiesYes (401 handling, user mgmt)
"bearer"Injects Authorization: Bearer <token>Yes
"custom"Editable stub — you implement your own headerYes

Generated Code

swoff/auth-fetch.js
import { fetchWithCache } from "./fetch-wrapper.js";
import { getAuth, clearAuth } from "./auth-store.js";

function withAuthHeaders(headers, auth) {
  // CLI generates this based on config: features.auth.type
  // "cookie": return headers (no-op)
  // "bearer": if (auth?.token) headers.set("Authorization", `Bearer ${auth.token}`)
  // "custom": // EDIT THIS — implement your custom header logic
  return headers;
}

function isAuthUrl(url) {
  const authPaths = [
    "/login", "/logout", "/register",
    "/api/login", "/api/logout", "/api/register",
    "/api/refresh", "/api/me",
  ];
  return authPaths.some((path) => url.includes(path));
}

export async function authenticatedFetch(input, options = {}) {
  const auth = await getAuth();
  const headers = new Headers(options.headers);
  withAuthHeaders(headers, auth);

  const url = typeof input === "string" ? input : input.url;
  if (isAuthUrl(url) && !headers.has("X-SW-Cache-Strategy")) {
    headers.set("X-SW-Cache-Strategy", "mutation");
  }

  const response = await fetchWithCache(input, { ...options, headers });

  if (response.status === 401) {
    await clearAuth();
    window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
  }

  return response;
}

Usage

import { authenticatedFetch } from "./swoff/auth-fetch.js";

// Authenticated read — cached by SW for offline
const profile = await authenticatedFetch("/api/me").then((r) => r.json());

// Authenticated mutation — passes through to server
await authenticatedFetch("/api/posts", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ title: "Hello" }),
});

// Login — bypasses SW cache entirely
await authenticatedFetch("/api/login", {
  method: "POST",
  body: JSON.stringify({ email, password }),
});

Every call to authenticatedFetch() runs getAuth() internally. After login, getAuth() returns the token from memory (instant). No manual token passing needed.

Token Refresh (Optional)

The CLI generates ensureValidAuth() inside swoff/auth-fetch.js when features.auth.refreshPath is set. Call it before any request that may need a fresh token:

import { ensureValidAuth } from "./swoff/auth-fetch.js";

const auth = await ensureValidAuth();
if (!auth) {
  // Token refresh failed, redirect to login
  return;
}
const data = await authenticatedFetch("/api/sensitive").then((r) => r.json());

Changing withAuthHeaders Based on Your Auth Type

Check Auth Libraries Reference to determine your category:

Auth CategorywithAuthHeaders()Example
Cookie/SessionNo-op (return headers as-is)Better-Auth default, Auth.js
Bearer Tokenheaders.set("Authorization", "Bearer " + auth.token)Auth0, Firebase, Supabase raw fetch
Custom HeaderSet your custom header nameDRF TokenAuth, API keys
SDK-managedSkip this file, use SDK's clientSupabase JS SDK, Better-Auth client

Next Steps

On this page