Service Worker Routing — Part 3: Authentication-Aware Routing 🔐
Lecture 7: Authentication Aware Routing 🔒
The Routes That Need Special Handling
Three routes are authentication-sensitive and can't be handled with the same logic as regular pages:
/login— should redirect to/add-postif already logged in; show the form if not/logout— must always hit the server to clear the session; never serve from cache/add-post— should redirect to/loginif not logged in; only usable when online
The SW now knows isLoggedIn from the status update messaging — which makes auth-aware routing possible.
The big takeaway: The service worker should mimic the server's own routing logic for these pages. The goal is for the user to have as close an experience offline as when the server is online.
Routing /login
Decision tree:
Online?
→ defer to the server (let it redirect or show login form)
Offline + logged in?
→ show /add-post from cache (user is probably still logged in)
Offline + logged out?
→ show /login from cache (same as what the server would do)
Nothing in cache?
→ /offline pageif (reqURL === '/login') {
if (isOnline) {
let res = await safeRequest(reqURL, request, {
cacheResponse: false,
checkCacheFirst: false,
checkCacheLast: false
});
if (res) return res;
}
// Offline
let res;
if (isLoggedIn) {
res = await cache.match('/add-post'); // assume still logged in
} else {
res = await cache.match('/login');
}
return res || cache.match('/offline');
}When online, defer entirely to the server — it knows the ground truth of whether the session is valid. When offline, make a best-guess based on isLoggedIn.
Routing /add-post
The inverse of login:
Online?
→ fetch the page
Offline + logged in?
→ serve /add-post from cache
Offline + logged out?
→ serve /login from cache (no point showing add-post if not authenticated)
Cache miss + logged out?
→ redirect to / (no useful action possible here)
All else fails?
→ /offline pageif (reqURL === '/add-post') {
if (isOnline) {
let res = await safeRequest(reqURL, request, {
cacheResponse: true,
checkCacheLast: true
});
if (res) return res;
}
// Offline routing
if (isLoggedIn) {
let res = await cache.match('/add-post');
return res || cache.match('/offline');
} else {
let res = await cache.match('/login');
if (res) return res;
return Response.redirect('/'); // nothing useful to show — go home
}
}Why redirect to / instead of /offline when logged out with no cache? Because we know the user isn't logged in — the offline page would be misleading. Sending them home is more honest.
Routing /logout — The Most Interesting One
Logout needs to work even offline. If the user clicks logout without a connection, the server can't clear the session — but the SW can simulate it by deleting the cookie from the page.
Online?
→ let server handle it → redirect to /
Online but server fails?
→ simulate logout: message the page to delete the cookie
→ wait 100ms for cookie deletion → redirect to /
Offline + logged in?
→ simulate logout: same cookie deletion trick → redirect to /
Offline + not logged in?
→ no-op → redirect to /if (reqURL === '/logout') {
if (isOnline) {
let res = await safeRequest(reqURL, request, { cacheResponse: false });
if (res && res.status === 0) { // opaque redirect from server
return Response.redirect('/');
}
// Server reachable but logout didn't redirect — force logout
if (res) {
await sendMessage({ forceLogout: true });
await new Promise(res => setTimeout(res, 100)); // wait for cookie deletion
return Response.redirect('/');
}
}
// Offline but logged in — simulate logout
if (isLoggedIn) {
await sendMessage({ forceLogout: true });
await new Promise(res => setTimeout(res, 100));
return Response.redirect('/');
}
// Offline + not logged in — nothing to do
return Response.redirect('/');
}The forceLogout message tells blog.js to programmatically delete the session cookie. Even though the server session may still technically exist, the user effectively can't use it — the cookie is gone. From the user's perspective, they're logged out.
"So even if you're offline and you click the logout link, it looks like it allows you to log out. It's deleted the cookie — so even though the session might still be on the server, you're effectively logged out."
Next up: Login & Logout Routing — the POST requests that handle the actual form submissions.
Lecture 8: Login & Logout Routing 🔑
Handling POST Requests for Auth Forms
The login and logout page navigations were covered in Lecture 7 — but the actual form submissions are POSTs that need separate handling.
// POST to /api/login
if (reqURL === '/api/login') {
if (isOnline) {
let res = await fetch(request.url, {
method: 'POST',
headers: request.headers,
credentials: 'same-origin',
body: request.body, // must forward the form data
cache: 'no-store'
});
if (res.ok) return res;
}
// Offline — can't log in, return a friendly error
return new Response(
JSON.stringify({ error: 'You must be online to log in.' }),
{ status: 403, headers: { 'Content-Type': 'application/json' } }
);
}Key point: body: request.body — unlike GET requests, POST requests carry a body (the form data). You must forward it explicitly in fetchOptions, or the server receives an empty request.
The Full Auth Routing Flow
GET /login
→ logged in? → redirect to /add-post
→ logged out? → fetch page → cache → return
GET /logout
→ online? → fetch (clears session) → return
→ offline? → redirect to /
GET /add-post
→ logged out? → redirect to /login
→ logged in + online? → fetch → return
→ logged in + offline? → serve from cache or /offline
POST /api/login
→ online? → fetch with body → return
→ offline? → synthetic 403 error responseNext up: Authentication Aware Routing Demo — seeing all the auth routing work in practice.
Lecture 9: Authentication Aware Routing Demo 🎬
The forceLogout Handler in blog.js
The other side of the logout trick — when the SW sends { forceLogout: true }, the page handles it:
// In blog.js onSWMessage handler
if (data.forceLogout) {
// Delete the session cookie
document.cookie = 'session=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
isLoggedIn = false;
sendStatusUpdate(); // tell the SW we're now logged out
// no redirect needed here — SW handles that in 100ms
}The SW sends the message, the page deletes the cookie and updates status, the SW waits 100ms then redirects. Clean separation of responsibilities.
What the Demo Confirms ✅
- Homepage offline — the API call that fetches post IDs gets cached on first visit. Go offline, refresh the homepage — post list still loads from cache.
- Blog posts offline — individual post pages get cached as they're visited. Go offline → previously read posts still load. Never-visited posts → friendly offline page (expected).
/add-postwhile logged in — visiting the page while online caches it. Go offline while logged in — the add-post form is still there and usable.- Posting while offline — the AJAX POST fails (expected — can't write to the server without a connection). The page shows "posting failed, try again." A future improvement: queue the post and retry when back online (Background Sync territory).
- Offline logout — clicking logout while offline → SW sends
forceLogout→ cookie deleted → redirect to homepage. User is effectively logged out.
The Achieved Goal
"I can still read your blog post when I get on a plane. That's the main use case solved."
Pages the user has visited load offline. The site degrades gracefully in every failure scenario — no blank screens of death, no cryptic browser errors. Just a working site that handles connectivity honestly.
Across all nine lectures:
- The fetch handler —
event.respondWith(),new URL(), same-origin filter - Kill switch pattern —
DISABLE_SWflag for safe development - Four caching strategies — network only, network first, cache first, stale-while-revalidate
- Three resource types, three strategies — APIs, HTML pages, static assets
- Synthetic responses —
Response.redirect(),notFoundResponse(), custom JSON errors X-Not-Foundheader — server-side signal to prevent caching friendly 404 pages- Auth-aware routing — using
isLoggedInandisOnlineto make smart routing decisions without touching the network - POST body forwarding —
body: request.bodyrequired for form submissions