Tip #7

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

ActionClick "Save" (after 1h)
Backend401 Unauthorized
ResultπŸ’₯ Redirect to Login
Data Lost. User is angry.

Resilient App

ActionClick "Save" (after 1h)
Backend401 Unauthorized
ResultπŸ›‘οΈ Silent Refresh
Success. Token refreshed, data saved.

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.

tests/test_chaos_silent_logout.py

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.

Start Testing

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."

← Previous TipThe "Zombie UI"
Next Tip β†’Coming Soon