Auth-Aware Caching
Auth headers, 401 detection, token refresh, protected routes, cross-tab sync.
Swoff's auth layer injects headers automatically, refreshes tokens on 401, and
clearAuth()purges memory, IndexedDB, caches, and cross-tab state in one call.
Preconditions
- A backend with login/logout endpoints and a session or token mechanism
swoff initalready run
Status
Disabled by default. Auth must be explicitly enabled — it's not part of the default generated config.
Enable
npx @swoff/cli add authDefaults to cookie auth (auth.type: "cookie"). To use bearer tokens:
# Edit swoff.config.json first, then:
npx @swoff/cli add authOr set features.auth.enabled: true and features.auth.type in swoff.config.json then regenerate.
Generated files
| File | What it does | Import in your code? |
|---|---|---|
swoff/auth/adapter.ts | Maps Swoff auth to your provider. Defines AuthData + exports adapter with getAuth, getHeaders, refresh, fetchUser. | Yes — import adapter for type reference |
swoff/auth/store.ts | setAuth(), getAuth(), clearAuth(), ensureValidAuth(), isAuthValid(), withAuthHeaders(), isAuthUrl(), AUTH_WITH_CREDENTIALS | Yes — main auth API |
swoff/auth/state.ts | getAuthState() — 4-state matrix (online/offline × authed/unauthed) | Yes |
swoff/auth/check.ts | isAuthFailureResponse() — customize what both the SW and client treat as auth failure | Yes — edit this file |
swoff-api.bundle.js | No-bundler equivalent — swoff.configure({auth: {...}}), swoff.setAuth(), swoff.clearAuth(), swoff.getAuthState(), swoff.clearMemoryAuth() on window.swoff | No (auto-initializes) |
Usage
Auth is framework-agnostic — the generated modules work the same in React, Laravel Blade, HTMX, or any setup.
Bundler projects
import { fetchWithCache } from "./swoff/fetch/core";
import {
setAuth,
clearAuth,
getAuth,
isAuthValid,
ensureValidAuth,
} from "./swoff/auth/store";
import { getAuthState } from "./swoff/auth/state";
// After login — pass the user data from your backend
const user = await fetchWithCache("/api/me", { auth: true }).then((r) =>
r.json(),
);
await setAuth(user);
// Authenticated requests — auth headers injected automatically
const { response } = await fetchWithCache("/api/protected", { auth: true });
// Check auth state anywhere
const { authenticated, auth, online } = await getAuthState();
// States: online+authed, online+unauthed, offline+authed, offline+unauthed
// On logout — clears memory, IDB, runtime caches, mutation queue, and broadcasts to other tabs
await clearAuth();No-bundler projects
Configure auth at runtime via swoff.configure(), then use the API on window.swoff:
<script src="/swoff/client-injector.bundle.js"></script>
<script src="/swoff/swoff-api.bundle.js"></script>
<script>
swoff.configure({
auth: {
type: "cookie",
getHeaders: () => ({}),
refresh: async () => null,
fetchUser: async () => null
}
});
const user = await swoff.fetchWithCache("/api/me", { auth: true });
await swoff.setAuth(user.data);
// Check auth state — 4-state matrix
const { authenticated, online } = await swoff.getAuthState();
// Memory-only auth clearing (does not touch IndexedDB)
await swoff.clearMemoryAuth();
// Full clear — memory, IDB, runtime caches, mutation queue, cross-tab
await swoff.clearAuth();
</script>For cookie auth (e.g., Laravel Sanctum, Django session), the browser sends the cookie automatically — no token management needed. For bearer auth, tokens are stored in memory only and never exposed to the Service Worker scope.
Bearer-only — token refresh is automatic when the SW detects a 401 on { auth: true } requests:
// The SW intercepts the 401, calls adapter.refresh() silently,
// retries the original request with the new token.
// Your code just sees the successful response.Customize
swoff/auth/adapter.ts (bundler) — you must edit this file
AuthData
The single source of truth for auth shapes across the client, store, and state modules:
export interface AuthData {
token?: string; // Bearer token (cookie auth omits this)
user?: unknown; // Replace `unknown` with your backend's user type
expiresAt?: number; // Unix ms — used by isAuthValid() / ensureValidAuth()
}Edit the user field type to match your backend's user object.
Adapter methods
The exported adapter object has four methods:
export const adapter: {
type: "cookie" | "bearer";
getAuth: () => Promise<AuthData | null>;
getHeaders: (auth: AuthData | null) => Record<string, string>;
refresh: (auth: AuthData) => Promise<AuthData | null>;
fetchUser: () => Promise<AuthData | null>;
};| Method | Purpose | Cookie behavior | Bearer behavior |
|---|---|---|---|
getAuth() | Return current auth from your provider. Used by getAuth() in store.ts as first lookup. | Return null — browser sends cookie automatically. | Return null — token lives in memory, not in adapter. |
getHeaders(auth) | Generate auth headers for fetchWithCache requests. | Return {} — browser sends cookie automatically. | Return { Authorization: "Bearer <token>" } if token present. |
refresh(auth) | Refresh the session when SW detects a 401. Called by ensureValidAuth() / SW on auth-failure. | Return null — server manages session lifetime. | POST to /api/refresh with existing token, return new { token, expiresAt }. |
fetchUser() | Fetch current user from server. Used by getAuth() in store.ts as last-resort fallback. | GET /api/me with credentials: "include". | GET /api/me with Bearer header. |
The type field controls AUTH_WITH_CREDENTIALS in store.ts (true for cookie, false for bearer).
swoff/auth/check.ts (bundler) — customize auth failure detection
Default: response.status === 401. Override if your backend uses a different signal:
export async function isAuthFailureResponse(response) {
// Custom header
return response.headers.get("X-Auth-Status") === "expired";
// Or JSON body (must clone first)
const data = await response.clone().json();
return data.error === "unauthorized";
}No-bundler: runtime auth adapter
The no-bundler API bundle (swoff-api.bundle.js) accepts an auth adapter via swoff.configure(). The adapter shape is the same as in auth/adapter.ts — pass getHeaders, refresh, fetchUser, and type at runtime instead of editing a generated file.
<script>
swoff.configure({
auth: {
type: "bearer",
getHeaders: (auth) =>
auth?.token ? { Authorization: `Bearer ${auth.token}` } : {},
refresh: async (auth) => {
const res = await fetch("/api/refresh", { method: "POST" });
return res.ok ? res.json() : null;
},
fetchUser: async () => {
const res = await fetch("/api/me");
return res.ok ? res.json() : null;
},
},
});
</script>For server-rendered pages, call swoff.configure() after the bundles load, before any auth-dependent fetchWithCache calls.
Framework adapters
This section has one adapter:
useSwoffAuth
Exposes auth state: { authenticated, auth, online, setAuth, clearAuth }.
Events listened to:
| Event | Detail shape | Purpose |
|---|---|---|
sw-auth-state-change | { type: "login" | "logout" | "refresh" | "clear" } | Auth state changed |
If your framework already has a useSwoffAuth adapter (React, Vue, Svelte), the CLI generates it into swoff/adapters/. To create adapters for any other framework, see the Blueprint for the event reference and adapter patterns.
Config
{
"features": {
"auth": {
"enabled": true,
"type": "cookie",
"routePaths": ["/api/login", "/api/logout", "/api/refresh", "/api/me"]
}
}
}auth.type—"cookie"(httpOnly session cookie),"bearer"(token), or"custom"auth.routePaths— URL paths that bypass SW cache (auth endpoints must not be cached)
Auth type vs feature compatibility
| Auth type | PWA | Mutation queue | Background sync | Server push | GraphQL |
|---|---|---|---|---|---|
| cookie | ✅ | ✅ | ✅ | ✅ | ✅ |
| bearer | ✅ | ✅ | ❌ | ❌ | ✅ |
| custom | ✅ | ✅ | ❌ | ❌ | ✅ |
Background sync and server push require cookie auth because they run in the SW scope (no DOM, no token refresh possible).
Related
- Library Comparison — auth module vs Workbox and others
- Auth Libraries Reference — library-specific
getHeaders()configuration