The "Silent Logout"
(Auth Chaos)
Why your users lose their work when they leave a tab open overnight.
The Problem: The "Session Trap" πΈοΈ
Standard App
Resilient App
A user fills out a long form, then gets distracted. They come back 30 minutes later and click "Save." Because their Auth Token (JWT) has expired, the API returns 401 Unauthorized.
The Default Behavior: The app detects the 401, immediately redirects the browser to /login, and destroys all the form data the user just typed. This is a catastrophic user experience.
The Solution: The "Mid-Flight" Revocation
Don't wait for tokens to expire naturally
Waiting 30 minutes for a token to expire makes testing this impossible. You need a way to force a meaningful expiration event exactly when the user clicks submit.
RECOMMENDED: Do use Header Manipulation
Let the user log in normally. Then, right before they perform a critical action (like "Checkout" or "Save Profile"), intercept the request and strip the Authorization header. This forces the backend to reject the request, triggering your app's "Session Recovery" logic.
The Code (Python + Playwright)
Here is a chaos test that simulates a session dying exactly at the worst moment.
def test_chaos_silent_logout(page):
# 1. Login normally
page.goto("/login")
page.fill("#email", "user@debuggo.app")
page.fill("#password", "password123")
page.click("#login-btn")
# 2. Fill out a critical form
page.goto("/settings/profile")
page.fill("#bio", "This is very important text I do not want to lose.")
# 3. πͺ INJECT CHAOS: Strip the Auth Token
# We intercept the 'save' request and delete the Authorization header
# to simulate an expired session.
def kill_auth_header(route):
headers = route.request.headers
if "authorization" in headers:
del headers["authorization"] # Remove the JWT
# Send the request without the token
route.continue_(headers=headers)
# Activate the interceptor ONLY for the save endpoint
page.route("**/api/profile/save", kill_auth_header)
# 4. Trigger the Save
save_button = page.locator("#save-btn")
save_button.click()
# 5. Verify Resilience (The "Plan B")
# BAD OUTCOME: We are redirected to login page (URL changed)
# assert page.url == "/login" <-- This means we failed.
# GOOD OUTCOME A: Silent Refresh
# The app caught the 401, used a refresh token, and retried automatically.
# Expect success message despite the initial failure.
expect(page.locator(".toast-success")).to_be_visible()
# GOOD OUTCOME B: The "Soft" Login
# A modal appears asking for a password, WITHOUT reloading the page.
# Expect the 'Bio' text to still be visible in the text area.
expect(page.locator("#login-modal")).to_be_visible()
expect(page.locator("#bio")).to_have_value("This is very important text I do not want to lose.")
Are you deleting user data?
Use Chaos Proxy to strip auth headers and ensure your app handles expiration gracefully. Test Auth Resilience.
No code required
Why this matters
This distinguishes "Stateless" code from "Stateful" UX.Developers often say, "The API returned 401, so we must redirect." Chaos QA says: "The user has unsaved data. Redirecting is forbidden. We must attempt to refresh the token, or popup a login modal, but never refresh the page."