TFOL Dev Story #2
How a single leftover session variable broke login, navigation, and reality
There are bugs that fail loudly.
There are bugs that fail silently.
And then there are bugs that only appear after everything seems to be working.
This is the story of one of those.
The Symptom (or so I thought)
Everything worked:
-
Guests could browse websites
-
Members could log in
-
Navigation menus rendered correctly
-
Website switching worked — mostly
But then a strange pattern emerged:
If I logged in, switched websites, logged out, and tried to log in again — the login form wouldn’t appear.
It just bounced back to the home page.
Worse:
-
Restarting the browser fixed it
-
Clearing cookies fixed it
-
Fresh sessions worked perfectly
Classic “it must be caching something” vibes.
It wasn’t.
The Pattern That Gave It Away
The bug only appeared after this sequence:
-
Start as Guest
-
Log in as a member
-
Switch websites
-
Log out (back to Guest)
-
Click “Log in” again
At step 5, the login page refused to render.
That told me something important:
Some piece of “logged-in identity” was surviving logout.
The Wrong Assumption
Like many PHP apps, TFOL treated this as “logged in”:
$_SESSION['id'] > 0$_SESSION['account_id'] > 0
And on logout or website switch, those values were being cleared.
So naturally I assumed the session was clean.
It wasn’t.
The Real Culprit: Identity Metadata Leak
After adding temporary logging inside login.php, the truth became obvious.
Even after logout, the session still contained:
{"id": 0,"account_id": 0,"role": "admin","forename": "Geoffrey"}
So although the user was technically logged out…
-
The role still said
admin -
The name still said
Geoffrey
That single leftover metadata caused:
-
Navigation to think a member existed
-
Links like
/member/0to be generated -
Login logic to redirect instead of render
The app was half-logged-in.
Why It Was So Hard to Spot
This bug had three properties that made it deceptive:
-
No errors
PHP didn’t complain. Routing didn’t break. -
State-dependent
Fresh sessions worked perfectly. -
Order-dependent
Only happened after login → switch → logout.
This wasn’t a syntax bug.
It was a session lifecycle bug.
The Fix (The Right One)
The solution was not more conditionals.
It was centralizing what “Guest” actually means.
When forcing Guest mode, clear all identity state:
$_SESSION['id'] = 0;$_SESSION['account_id'] = 0;unset($_SESSION['role'],$_SESSION['forename'],$_SESSION['surname'],$_SESSION['email'],$_SESSION['member'],$_SESSION['menu_owner_id'],$_SESSION['section']);$_SESSION['role'] = 'guest';session_regenerate_id(true);
And then apply this everywhere it matters:
-
Website switching
-
Logout
-
Any forced guest transition
Hardening the UI (Defense in Depth)
Navigation was updated to treat users as logged in only if both are true:
{% set is_logged_in =session.id|default(0) > 0and session.account_id|default(0) > 0%}
So even if metadata ever leaks again, it won’t generate invalid member routes.
The Lesson
Session bugs are not about authentication.
They’re about identity boundaries.
Logging out isn’t just “unset the ID”.
It’s:
-
Clearing identity
-
Clearing role
-
Clearing cached objects
-
Resetting expectations
If any one of those survives, reality bends.
Final Takeaway
If a bug:
-
Only appears after logout
-
Disappears on browser restart
-
Refuses to reproduce in clean sessions
Then it’s almost always this:
Something in the session still thinks you’re someone else.
And the fix isn’t clever logic —
it’s ruthless cleanup.
Posted in tfol-dev-stories by TFOL BLOG