My task app, todo., is as self-hosted as an app can be. An open-source backend runs on the server in my closet. I built a small PWA front-end. The app is reachable only inside my private network. It has exactly one user: me.
During setup, I gave the login tokens a ten-year lifetime. A single-user app must never ask its single user to log in again before 2036.
It asked me to log in about every week.
The token was always valid
The folklore explanation was “the ten-year token setting must not work.” I finally sat down and checked, instead of a fortieth password entry. I decoded a real token. The expiry claim was ten years out, exactly as configured. The token was valid. The token was always valid.
One line of my own code
The real cause was one line of my own front-end code. On any 401 response, the app deleted the stored token and sent me to the login screen. That is textbook behavior. A 401 means your credentials are bad, correct? No. A 401 is not only “your credentials are bad.”
The backend restarts. The reverse proxy stumbles. The phone version of the app wakes from the dead in the middle of a request. Each of those can put a transient error on a valid token. So my app’s response to a network sneeze was to destroy a decade of credentials and demand proof of my identity. Multiply that by three devices, each with its own stored copy of the token. The weekly login screen stops being a mystery.
The doorman question
I started to design the fix. Retry logic, smarter 401 handling, refresh-token care. Then I stopped, because the frame was wrong. This app has one user. It runs on a network with a perimeter, on hardware I own. Why does it have a login screen at all? A login screen on a single-user app is a doorman for your own bathroom.
The doorman came from a copy of public web apps, not from a real threat. Anyone who can reach the app is already inside my network. If a stranger is inside my network, my task list is the least of my problems.
Let the proxy vouch
The architecture I chose: the reverse proxy vouches for you. When a request arrives from inside the perimeter, the proxy injects the credentials on the server side. The app never shows a login screen. The front-end never touches a token, so the front-end cannot lovingly destroy one. The API keeps normal token auth for scripts and integrations. I did not handle the 401 better. I made the 401 structurally impossible.
The bug left me a rule for my self-hosted, single-user world. Give every login screen a second look. It can be a piece of someone else’s architecture that I forgot to delete.