Token Storage
Store auth tokens securely using IndexedDB + memory cache.
Token Storage
Prerequisites: None. This module is self-contained and independent of your auth mechanism.
Storage Strategy
| Storage | Persists | Scope | Contains |
|---|---|---|---|
| Memory | Until page refresh | Tab | Bearer token (if auth type is "bearer") |
| IndexedDB | Indefinitely (swoff-auth DB) | Tab | { user, expiresAt } only — no token |
The CLI generates this file when features.auth.enabled is true. For "bearer" auth, the token lives in memory only — page refresh requires re-login. { user, expiresAt } is persisted to IndexedDB for offline display. For "cookie" auth, the browser handles credentials automatically.
The bearer token is never written to IndexedDB. It only lives in memory and is lost on page refresh. This is intentional — IndexedDB has the same origin-level access as any JavaScript on your origin. For high-security apps, prefer http-only cookies over bearer tokens.
Implementation
// swoff/auth-store.js — Generated by CLI. Token is memory-only.
// Persists only { user, expiresAt } to IndexedDB for offline display.
const DB_NAME = "swoff-auth";
const STORE_NAME = "auth";
let memoryAuth = null;
function openAuthDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: "key" });
}
};
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(e.target.error);
});
}
export async function setAuth(authData) {
memoryAuth = authData;
const db = await openAuthDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, "readwrite");
const store = tx.objectStore(STORE_NAME);
// Persist only user + expiresAt — no token
store.put({ key: "session", value: { user: authData.user, expiresAt: authData.expiresAt } });
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
export async function getAuth() {
if (memoryAuth) return memoryAuth;
const db = await openAuthDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, "readonly");
const store = tx.objectStore(STORE_NAME);
const request = store.get("session");
request.onsuccess = () => {
const persisted = request.result?.value || null;
if (persisted) memoryAuth = { ...persisted, token: undefined };
resolve(memoryAuth || null);
};
request.onerror = () => reject(request.error);
});
}
export async function clearAuth() {
memoryAuth = null;
const db = await openAuthDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, "readwrite");
const store = tx.objectStore(STORE_NAME);
store.delete("session");
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
export function isAuthValid(auth) {
if (!auth) return false;
if (!auth.expiresAt) return true;
return Date.now() < auth.expiresAt;
}Your Login / Logout
These are your functions — Swoff provides the storage infrastructure:
// In your app — you provide login/logout, Swoff provides storage
import { setAuth, clearAuth } from "./swoff/auth-store.js";
async function login(email, password) {
// Your auth mechanism: JWT, session, OAuth, whatever
const response = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ email, password }),
});
const data = await response.json();
// Store in Swoff's auth infrastructure
await setAuth({
token: data.token, // Your token (or null if http-only cookie)
user: data.user, // Current user object
expiresAt: data.expiresAt, // Expiry timestamp (ms), or omit
});
window.dispatchEvent(
new CustomEvent("sw-auth-state-change", {
detail: { authenticated: true },
}),
);
}
async function logout() {
await fetch("/api/logout", { method: "POST" }).catch(() => {});
await clearAuth();
window.dispatchEvent(
new CustomEvent("sw-auth-state-change", {
detail: { authenticated: false },
}),
);
}What authData Should Contain
The shape depends on your backend:
| Field | Required? | Purpose |
|---|---|---|
token | Cookie: no / Bearer: yes | The token string to attach to requests |
user | Recommended | Current user object for offline display |
expiresAt | Optional | Timestamp (ms) for offline expiry checking |
Next Steps
- Auth Libraries Reference — identify your auth type
- Auth-Aware Fetch — attach credentials to requests
Swoff