Skip to content

The One-Line Catch That Deleted Admin Sessions

Adityo Guni Waluyo

An API outage and an expired session were treated the same by a single catch, until one server restart logged out every admin.

TL;DR

A single catch-all deleted the session token on any error, so a brief backend outage logged out every admin. The fix: classify failures first—only 401 purges the token, 403 shows an access-denied notice, and 5xx or network errors keep the session with a retry banner. Five unit tests lock the classification.

The backend API restarted for a moment, and every logged-in admin was thrown back to the login screen. No message, no warning. A perfectly valid session was treated as expired simply because the server was briefly unreachable.

The culprit was one line. Session bootstrap in the admin shell had a single catch: any failure immediately deleted the stored token. A 5xx response, a dropped connection, and a genuinely expired session were all treated identically. A momentary outage looked exactly like a logout.

Why one catch is not enough

The problem was never the absence of error handling; it was handling that did not distinguish the identity of the failure. The fetch specification on MDN explains the difference in paths plainly: a fetch promise only rejects when the request fails at the network level or the URL is invalid; responses with HTTP error statuses resolve the promise normally, and the status is inspected through the response's properties [6]. An outage and a 401 therefore arrive through two different doors, and treating them the same inside one catch throws that information away.

From a security standpoint, deleting a session when the server returns 5xx is not caution. OWASP writes that a security mechanism should fail through the same path as an explicit denial [2], but what happened here is different: a valid session was destroyed because infrastructure hiccupped. That is not protective fail-closed behavior; it is a misclassification that punishes the user.

401, 403, and 5xx get three treatments

The fix starts with a classifier function, shouldClearSession: the token is deleted only when the error is an AdminApiError with status 401. MDN defines 401 as the response for a request that lacks valid authentication credentials [4]. Only that scenario justifies automatic deletion, because the identity truly cannot be verified anymore.

403 is treated differently. The server understood the request but refuses it, and MDN notes that repeating the request without modification will fail the same way [4]. So 403 must trigger neither a retry nor token deletion; the token is likely fine. The interface shows an access-denied notice with an explicit exit button back to the login page.

5xx and pure network failures get the third treatment: the session is kept. The panel displays a non-fatal banner with a retry button that re-runs bootstrap. Once the server recovers, the admin lands back on the dashboard without retyping credentials.

Five tests lock the classification

Even a classification this small needs a lock. Five unit tests cover the spectrum: 401 returns true; 403, 500, a network-failure TypeError, and undefined all return false. The list makes an implicit pattern explicit: widening the purge behavior now requires a deliberate reason and an accompanying test.

One implementation detail is easy to miss: the 401-versus-everything distinction lives in one small function, not scattered through the layout. The layout component just asks that function, then picks between three renders: the login screen, the access-denied banner, or the outage banner with its retry button. A decision function that centralized is exactly why five tests could be written without touching the DOM at all. A quick way to feel the difference: take the backend down while the admin panel is open. The single-catch version dumps you at login instantly; the classified version holds the banner and reconnects the session once the server returns.

The security side is honest too: purging only on 401 is not looser, it is more precise. A token the server has revoked still disappears from local storage on the first request that hits a 401. What changed is only the fate of a session when the credentials are not the problem. The escalation path is orderly now. 401 is answered with the login screen, no drama. 403 is answered with the uncomfortable truth: this account genuinely lacks access, and waiting will not change it. 5xx is answered with patience: the problem is at the server, not with you.

The distinction between 401 and 403 is easy to mix up, and the cost usually stays invisible until the server actually breaks. Classifying failures before acting on them turns an outage from a shock into a procedure: troubled credentials get resolved as fast as possible, troubled infrastructure gets room to recover.

Sources:

Related articles