All posts

January 29, 2026 · 5 min read

The Page Checked My Login. The API Never Did.

Here's a bug I keep running into, and it never shows up where you'd think to look. You open an app while logged out, try to hit a page you shouldn't have access to, and the app does exactly what it's supposed to: it kicks you to a login screen. Everything about the experience says locked down. Then you open dev tools, and the lock isn't actually where you thought it was.

Two different questions, and usually only one gets answered

A page redirecting you to /login answers one question: should this person be looking at this screen. That's a UI decision. It has nothing to do with a second, completely separate question: should this specific request, from this specific user, get this specific data back from the server. That second question has to be answered by the code running on the API route itself, every single time it's called, regardless of how the visitor got there.

Those are two different pieces of code, often written at two different times, sometimes by two different prompts. It's entirely possible to get the first one perfect and never touch the second.

Walking through a hypothetical

Picture a small project tracker. There's a page at /dashboard/reports/42 that shows one client's report. Try to load it logged out and you get bounced to /login. So far, so good, the page is doing its job.

But that page is mostly a shell. Once it loads, it fires off a request in the background, something like GET /api/reports/42, to actually pull the report content. That request runs a completely different piece of server code than the one that decided whether to show you the page or bounce you. If nobody went back and added an authorization check inside that API handler, it will happily return the report to anyone who asks it directly, login screen or not.

Here's how you'd actually notice this from the outside, or on your own app if you're checking your own work:

  • Load the page normally while logged in, open dev tools, go to the Network tab.
  • Find the request that's pulling the real data, usually the one returning JSON, not HTML.
  • Right click it, copy as curl.
  • Open a private browser window (no cookies, no session) and run that same curl command, or just strip the auth header out of it yourself.
  • Look at what comes back.

If real data comes back with zero session attached, the page was never protected. The button to get there was.

There's a second version of this same bug that's just as common and a little sneakier. Stay logged in as yourself, but change the 42 in that URL to 43. If you get someone else's report back, the server still isn't checking anything, it's just trusting whatever ID it's handed. Same root cause as the first case: nobody asked whether this request belongs to this user. It's just a different way of noticing it.

Why this pattern shows up so often right now

It's not that anyone decided to skip security. It's that a missing redirect is visible and a missing API check is invisible. A page that lets a logged out user through will show you a broken screen, something to point at, something that obviously needs fixing before a demo. A missing check on the API underneath produces no symptom at all. The page renders. The demo works perfectly. Nothing looks wrong until someone goes looking on purpose.

Framework setup makes it easy to miss too. A lot of these apps configure their auth middleware to match page paths, something like /dashboard/*, and whoever set that up just never added /api/* to the same list. The gate is real. It's just standing next to the door instead of built into it.

The test that actually tells you something

For your own project, stop asking whether a page requires login. Ask whether each individual server function refuses to answer when the request has no valid session, or the wrong one. That check has to live on the server, on every route that returns anything sensitive, completely independent of whatever the page did to get someone there.

  • List every API route in the project. Not every page, every route.
  • For each one, ask: what happens if I call this directly with no cookie at all.
  • For each one that takes an ID, ask: what happens if I'm logged in as myself but pass in someone else's ID.
  • Anything that returns real data in either case is a fix, not a note for later.

What I keep coming back to is that a login redirect is a UI decision and an authorization check is a server decision, and it's completely possible to ship an app where the first one is flawless and the second one doesn't exist. If the only way you've ever tested your app is by clicking around it logged out, you've tested the door. You haven't tested whether there's a wall around it at all.

Related reading

Harbova is a security service for apps built with AI tools. Start with a free scan, and if it finds something serious, we can fix it and prove it is closed.