Most people think of authentication as a solved problem. You check a username and password, and you’re done. I thought the same thing until I had to overhaul authentication in two of my projects: the Mailroom Management System and the Survey Builder. Both had auth that technically worked but had real problems, and fixing them taught me more than I expected about how much complexity hides behind a login screen.
Fixing the Mailroom application
The Mailroom application originally used JWT authentication. Passwords were hashed, but I’d skipped refresh tokens, so after a few hours users would just get logged out. On top of that, if an admin changed a user’s role or name, the user had to manually log out and back in for the changes to take effect. I knew about these problems when I shipped the initial version, but I didn’t have time to fix them before the deadline.
I migrated to cookie-based authentication with .NET, and the transition was smoother than I expected. I replaced the JWT middleware with .NET’s built-in claims management, where claims get assigned during login and stored in a cookie, and .NET handles authorization from there. That cut a lot of redundant code and left me with a simpler codebase and auth that actually works properly.
I have mixed feelings about .NET for web development. It’s monolithic and the default UI framework (Bootstrap) is showing its age. However, the auth system is genuinely well-designed, and the developer experience for this kind of migration was better than I expected.
Fixing the Survey Builder’s OAuth2 flow
The Survey Builder integrates with OAuth2 providers like FusionAuth and Auth0. Users were getting randomly redirected to the login page in the middle of their sessions, and if the app kicks you out while you’re working, you stop trusting it. It turns out the root cause was unreliable session persistence.
The way I fixed it was by implementing the offline_access scope and reworking how tokens were stored. The system now tracks refresh tokens alongside their creation, expiry, and deletion dates, so sessions renew properly through the SSO provider. The random logouts stopped, and working through the problem forced me to actually understand OAuth2’s token lifecycle rather than just following the documentation.
Why I kept the auth methods separate
I did consider switching the Mailroom application to OAuth2 as well, but I decided against it. Part of it is practical: cookie-based auth is the right fit for that application, and adding an external identity provider would just be complexity for its own sake. The other part is intentional: I wanted my projects to demonstrate different authentication approaches. JWT, cookies, and OAuth2 each have different tradeoffs, and having worked with all three gives me a clearer sense of when to reach for which.
I’ve also worked with SAML and Shibboleth in other contexts, and federated authentication is interesting, especially in university systems where dozens of separate identity providers need to work under one umbrella. However, adding that to a personal project would be overengineering.
The hidden complexity
People reduce authentication to “matching credentials” a lot, but the actual work is everything that happens after the login form. Token lifecycle management, session persistence, claims propagation, renewal flows. The login screen is maybe 5% of the problem.
Shibboleth is a good example of this kind of depth. It takes the already complicated problem of authentication and scales it across entire university systems, unifying dozens of separate SSO implementations under one federated layer. OAuth2’s authorization flows are the same way. When you actually dig into the spec, every piece is there for a reason. These aren’t over-engineered frameworks. They’re responding to real problems that come up when you try to scale auth beyond a single application.
What I took away
Fixing these two applications didn’t teach me anything conceptually new. I already knew what refresh tokens were, and I understood OAuth2 flows in theory. However, there’s a real difference between understanding something on paper and actually debugging why sessions randomly die. The hands-on work filled in gaps I didn’t know I had.
The other takeaway is simpler: good authentication is invisible. When it works, nobody thinks about it. When it breaks, nothing else in the application matters. That’s what makes it worth getting right.