If you've worked on any mid-sized ASP.NET application that uses Session to hold user or transaction context, you've probably run into a bug that looks impossible to reproduce — until you realize the user had two browser tabs open.
Here's the exact scenario I ran into, and why it's more dangerous than it first looks.
The Setup
A user logs in with one ID. That's one Session on the server, tied to that one ASP.NET_SessionId cookie.
Now the user opens two tabs of the same app:
- Tab 1: opens Employee A's record — who is eligible for Transaction Type 1
- Tab 2: opens Employee B's record — who is eligible for Transaction Type 2
Both tabs are using the same browser session cookie, which means both tabs are reading and writing to the exact same Session object on the server.
Where It Breaks
Say the app stores the currently selected employee and their eligibility flag in Session:
csharp
Session["CurrentEmployeeId"] = employeeId;
Session["EligibleTransactionType"] = eligibilityType;
The user is working in Tab 1 (Employee A), then switches to Tab 2 and opens Employee B. That action overwrites Session["CurrentEmployeeId"] and Session["EligibleTransactionType"] with Employee B's values.
Now the user switches back to Tab 1 and clicks Save. The save action reads eligibility from Session["EligibleTransactionType"] — but that value now belongs to Employee B, not Employee A. If there's no server-side re-validation at the point of save, Employee A's transaction gets saved using Employee B's eligibility rules.
No exception is thrown. No error is logged. The data just quietly becomes wrong.
Why This Is Worse Than a Normal Bug
- It's intermittent — only happens with multiple tabs, so it's hard to reproduce in testing
- It passes all standard QA — single-tab test cases work fine
- It causes silent data corruption, not a crash — which means it can sit undetected in production for a long time
- It often gets misdiagnosed as a "random" data issue rather than a session-scoping bug
The Root Cause
The core misunderstanding is this: Session in ASP.NET is scoped to the user's session cookie, not to a browser tab. All tabs in the same browser share the same session cookie by default, so they share the exact same Session object on the server. There is no built-in per-tab isolation.
Junior devs often mentally model Session as "this user's current screen state," when it's really "this user's shared server-side bucket, accessible from anywhere they have a tab open."
How to Actually Fix It
1. Never trust Session state alone for validation at save time.
Re-fetch and re-validate eligibility from the database (or source of truth) at the moment of save, using the entity ID that's actually being saved — not whatever happens to be sitting in Session.
csharp
Bad: trusts session blindly
if (Session["EligibleTransactionType"].ToString() == requestedType) { Save(); }
Better: re-validate against the actual record being saved
var currentEligibility = _employeeService.GetEligibility(employeeIdFromForm);
if (currentEligibility == requestedType) { Save(); }
2. Pass context explicitly through the request, not implicitly through Session.
Instead of relying on Session["CurrentEmployeeId"], pass the employee ID as a hidden field, route parameter, or query string on each screen. Each tab then carries its own context in the request itself, not in a shared server bucket.
3. If you truly need tab-level isolation, use TempData or per-request tokens carefully — not Session.
Session is inherently shared across tabs; if isolated state per tab is a real requirement, look at approaches like encoding context in the URL, using unique form tokens per screen instance, or client-side state management instead of leaning on Session.
4. Add server-side authorization/business rule checks as a final gate before any write.
Treat every save action as if the client could send anything — because in a multi-tab scenario, it effectively can. The last line of defense should always be a fresh check against the source of truth, not a value that was set several clicks ago.
The Takeaway
Session feels like it belongs to "this screen," but it actually belongs to "this user, everywhere they're logged in right now." Any app doing multi-step or multi-screen validation needs to treat Session as a convenience cache, never as the final source of truth at the point of a write. The fix isn't complicated — re-validate on save — but the bug is easy to miss precisely because it only shows up when real users behave in ways your test cases didn't cover.
If your app has any workflow where a user might reasonably have two screens open at once, it's worth auditing every Session-dependent save action today, before it becomes a production incident.
Top comments (0)