Defense in depth
Don't rely on one auth check. Layer them — each catches what the others miss.
| Layer | Catches |
|---|---|
| Proxy | Bots and casual users hitting protected URLs unauthenticated |
| Layout | Server-rendered pages that someone reaches without going through the proxy (e.g. preview deployments) |
| Server Action / Route Handler | Programmatic POSTs from anywhere on the internet |
What each layer is for
Proxy is the redirect/UX gate. Layout is the "rendering this page would leak data" gate. Action/handler is the "writing data could happen even without rendering a page" gate. The last one is the security gate — the only one you cannot skip.
Layer access checks by responsibility. Proxy may redirect early, a layout may shape the signed-in experience, but every Server Action, Route Handler, and data-access function must enforce the permission required for that operation. Put the decisive check at the deepest trusted boundary. Defense in depth is not copying the same boolean into three files. Each layer answers a different question, and duplicated policy drifts. Centralize the domain authorization rule, then call it from every mutation and sensitive read that needs the rule.
Open the protected URL normally, call its handler directly, invoke its action with another user's ID, and attempt the underlying data path from a test. All routes should converge on the same deny decision while redirects remain a UX convenience.