Three layers decide whether a request is allowed through, each cheaper and more permissive than the one below it.
Each layer can refuse independently. The proxy is never trusted alone.
Each tenant has its own Neon Auth instance, a managed Better Auth, with its own signing keys. A user signs in on the tenant's host, the auth instance issues a token signed by that tenant's keypair, and a cookie carries the session. Because the auth instance runs on Neon's domain and the app on its own, a proxy route on the app's origin bridges them so the session cookie lands on the app's domain.
Users live in the tenant's database, so the platform's own tables can join against them: onboarding, the pending-admin handoff, and the claim flow that hands a store from the console to its owner all work on local rows.
The sign-in page is kikstart's own, rendered on the tenant's host under the tenant's theme tokens, so logo, colour and copy are already tenant-controlled.
authenticated database role.The app never tells the database who the user is. Neon does.
Server actions call the session check on entry, because an action is its own entry point and the page gates never ran for it. Admin pages do not: every admin route renders inside the route gate, so a second check there can never fire and only costs a second cookie read that keeps the page out of its static shell.
API route handlers answer a signed-out caller with a 401 rather than a redirect to a sign-in page.
A user holds one role. What is plural is the role's capabilities: a set of permission codes, each <resource>.<verb>. Every resource has a manage; only resources whose reads are actually gated carry a read.
The database resolves a session's role to its permission codes, and every resource-gating policy compares one. An admin passes because the admin role holds every permission, not because the check special-cases it. Only the role ladder itself, assigning roles and guarding the last admin, still asks whether the caller is an admin, so a permission grant can never widen the set of permissions.
The same permission codes gate the admin sidebar and the routes behind it from one table, so a typed URL cannot reach a page the sidebar would hide. The roles screen edits a role's permission set; it revokes before it grants, so a failure between the two leaves fewer permissions, never more.
Two roles ship with every tenant: admin and user. A business process maps onto those. A field manager is an admin. More roles are supported but are downstream custom work.
A test harness signs in with one credential pair, an admin of the tenant under test. Playwright posts to the sign-in API and stores the session state. A browser session takes a development-only route that signs in server-side and redirects with the cookies set, so the password never leaves the server. That route answers 404 in production or when the credentials are unset.