Protecting Pages
How to require authentication across pages, layouts, and APIs
StarterApp keeps permission checks on the server. The client can show friendly messages, but the server decides who may see or change data. This guide shows where to place those checks without getting lost in implementation details.
Use the helpers, not hand-written logic
requireUser(), getCurrentSession(), and the CSRF helpers already include the edge cases we’ve hit. Reach for them first.
Pages and layouts
-
Import from
~/lib/auth/server(alias forpackages/app-shell/src/lib/auth/server.ts). -
Choose the helper:
requireUser()→ redirects to/sign-inwhen unauthenticated.getCurrentSession()→ returns a user object ornullso you can show different content.
-
Export the cache killers so personalised content never gets cached:
export const revalidate = 0; export const dynamic = "force-dynamic"; export const fetchCache = "force-no-store";
Typical usage:
- Layouts under
/app/(dashboard)callrequireUser()to protect all nested routes. - Marketing pages call
getCurrentSession()when they want to adjust copy for returning customers.
Client components
- Use
useSession()to show a loading indicator or “please sign in” message while the server decides what to serve. - For real-time updates (for example, active organisation name), subscribe with
useQuery(api.auth.getCurrentUser). - Remember: client checks are cosmetic; always pair them with a server-side check.
API routes and server actions
- Call
auth.api.getSession({ headers: await headers() })to retrieve the signed-in user. - For
POST,PUT,PATCH, orDELETE, run the CSRF guard:assertOrigin(request)assertFetchMetadata(request)
- Return responses with
secureUserJson(success) orsecureErrorJson(error) so headers stay consistent and private data is not leaked.
The template llms/templates/auth-api-route-template.ts shows the full pattern if you need a quick refresher.
Convex functions
- Use the identity helpers from
convex/lib/identity.ts:getUserIdOrThrow(ctx)throws an auth error when the request is unauthenticated.assertOwnerByUserId(record, viewerId, { concealExistence })hides whether a record exists when the user does not own it.
- Prefer the
userQuery,userMutation, anduserActionbuilders fromconvex/_helpers/builders.ts; they inject the authenticated user ID for you. - Apply
withRateLimitfor actions that write or trigger outbound requests.
Middleware
apps/dashboard/middleware.ts only handles security headers. Authentication checks belong in pages, layouts, API routes, or Convex functions where you have the full context and types.
Quick checklist
- Protected routes export the cache killers.
- Server code uses
requireUser()orgetCurrentSession()instead of reading cookies manually. - API routes combine
auth.api.getSession()with CSRF checks. - Convex functions call the identity helpers.
- Responses use
secureUserJson/secureErrorJson.