Please, Stop Redirecting to Login on 401 Errors 🛑

Published: (January 15, 2026 at 04:57 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

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);
});

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.

A robust app handles this flow:

  1. Intercept – Catch the 401 error.
  2. Queue – Pause the failed request; do not reload the page.
  3. Refresh – Obtain 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 a “silent refresh” is tricky, and testing it can be annoying. Access tokens usually last an hour, so you can’t ask QA to “wait 60 minutes and then click Save”.

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

The “Chaos” Approach

Instead of waiting for the token to expire naturally, delete it mid‑flight.

Using Playwright, you can intercept the outgoing request and strip the Authorization header before it reaches the server. This forces the backend to reject the request, triggering your app’s recovery logic immediately.

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
        # Manually delete the token to simulate expiration
        if "authorization" in headers:
            del headers["authorization"]
        # Send the "naked" request; backend will return 401
        route.continue_(headers=headers)

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

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

    # 5. Verify the app recovered
    expect(page.locator("#bio")).to_have_value("Important text I don't want to lose.")
    expect(page.locator(".success-message")).to_be_visible()

Summary

Network failures and expired tokens are facts of life. Your app should handle them without punishing the user. 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 at the network level.

Back to Blog

Related posts

Read more »

Axios

!Cover image for Axioshttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com...