DEV Community

Cover image for Please, Stop Redirecting to Login on 401 Errors πŸ›‘
Ilya Ploskovitov
Ilya Ploskovitov

Posted on • Originally published at chaos-proxy.debuggo.app

Please, Stop Redirecting to Login on 401 Errors πŸ›‘

You spend 15 minutes filling out a long configuration form. You get a Slack notification, switch tabs, reply to a colleague, and grab a coffee.

30 minutes later, you come back to the form and click "Save".

The page flashes. The login screen appears.And your data is gone.

This is the most annoying UX pattern in web development, and we need to stop doing it.

The "Lazy" Pattern

Why does this happen? Usually, it's because the JWT (access token) expired, the backend returned a 401 Unauthorized, and the frontend code did exactly what the tutorials said to do:

// Don't do this
axios.interceptors.response.use(null, error => {
  if (error.response.status === 401) {
    window.location.href = '/login'; // RIP data πŸ’€
  }
  return Promise.reject(error);
});
Enter fullscreen mode Exit fullscreen mode

Developers often argue: "But it's a security requirement! The session is dead!"

Yes, the session is dead. But that doesn't mean you have to kill the current page state.

The Better Way (Resilience)

If a user is just reading a dashboard, a redirect is fine. But if they have unsaved input (forms, comments, settings), a redirect is a bug.

Here is how a robust app handles this:

  1. Intercept: Catch the 401 error.

  2. Queue: Pause the failed request. Do not reload the page.

  3. Refresh: Try to get a new token in the background (using a refresh token) OR show a modal asking for the password again.

  4. Retry: Once authenticated, replay the original request with the new token.

The user doesn't even notice. The form saves successfully.

How to test this? (The hard part)

Implementing the "Silent Refresh" is tricky, but testing it is annoying.

Access tokens usually last 1 hour. You can't ask your QA team to "wait 60 minutes and then click Save" to verify the fix.

You need a way to trigger a 401 error exactly when you click the button, even if the token is valid.

The "Chaos" Approach

Instead of waiting for the token to expire naturally, we can just delete it "mid-flight."

I use Playwright for this. We can intercept the outgoing request and strip the Authorization header before it hits the server.

This forces the backend to reject the request, triggering your app's recovery logic immediately.

Here is a Python/Playwright snippet I use to verify my apps are "expiry-proof":

def test_chaos_silent_logout(page):
    # 1. Login and go to a form
    page.goto("/login")
    # ... perform login logic ...
    page.goto("/settings/profile")

    # 2. Fill out data
    page.fill("#bio", "Important text I don't want to lose.")

    # 3. CHAOS: Intercept the 'save' request
    def kill_token(route):
        headers = route.request.headers
        # We manually delete the token to simulate expiration
        if "authorization" in headers:
            del headers["authorization"]

        # Send the "naked" request. Backend will throw 401.
        route.continue_(headers=headers)

    # Attach the interceptor
    page.route("**/api/profile/save", kill_token)

    # 4. Click Save
    page.click("#save-btn")

    # 5. Check if we survived

    # If the app is bad, we are now on /login
    # if page.url == "/login": fail()

    # If the app is good, it refreshed the token and retried.
    # The text should still be there, and the save should succeed.
    expect(page.locator("#bio")).to_have_value("Important text I don't want to lose.")
    expect(page.locator(".success-message")).to_be_visible()
Enter fullscreen mode Exit fullscreen mode

Summary

Network failures and expired tokens are facts of life. Your app should handle them without punishing the user.

If you want to build high-quality software, treat 401 Unauthorized as a recoverable error, not a fatal crash.


PS: If you need to test this on real mobile devices where you can't run Playwright scripts, you can use a Chaos Proxy to strip headers on the network level.

Top comments (10)

Collapse
 
nadeem_rider profile image
Nadeem Zia

Great work :)

Collapse
 
aragossa profile image
Ilya Ploskovitov

Thanks for reading! Glad you found it useful. :)

Collapse
 
derrickrichard profile image
Derrick Richard

I don't believe I have seen anybody do it like this before (probably because they did exactly what you described 🀣). I think this article is very helpful. You have an awesome way to prevent unsaved data from being lost. Nice Work!

Collapse
 
aragossa profile image
Ilya Ploskovitov

Thanks! I actually lived through this nightmare myself. The app I was testing behaved exactly like thatβ€”every morning started with a forced re-login loop until I finally convinced my Tech Lead that we had to get rid of it. πŸ˜…

Collapse
 
natalie_david_831cd3993e4 profile image
Natalie David

Solid points overall .. preserving unsaved user input is definitely worth the extra engineering effort, especially in content-heavy apps (CMS, forms, editors).That said, in some API-first / public-facing products I’ve worked on, we sometimes do want the hard redirect on 401 for security hygiene reasons (force re-login after long inactivity, reduce attack surface on stolen refresh tokens). But even then, the right compromise is:For stateful UI / forms β†’ intercept + silent refresh / modal

For read-only / public-ish pages β†’ allow redirect

The real crime is applying the same naive redirect logic everywhere without thinking about context.Appreciate the reminder and the testing snippet , going to steal that route handler trick for our next chaos day.

Collapse
 
aragossa profile image
Ilya Ploskovitov

You hit the nail on the head regarding context.
I completely agreeβ€”a hybrid approach is the sweet spot. For read-only dashboards, a hard redirect is totally fine (and safer). But for active forms, that context switch is painful.
The 'naive redirect everywhere' is definitely the main enemy here. Glad you found the Playwright snippet useful, let me know how the Chaos Day goes!

Collapse
 
ashishsimplecoder profile image
Ashish Prajapati

Honestly, this is great solution.

Collapse
 
aragossa profile image
Ilya Ploskovitov

Appreciate it! It’s surprising how often we sacrifice user experience for 'security' by default, even when we can actually have both.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.