StarterApp Docs
Authentication

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

  1. Import from ~/lib/auth/server (alias for packages/app-shell/src/lib/auth/server.ts).

  2. Choose the helper:

    • requireUser() → redirects to /sign-in when unauthenticated.
    • getCurrentSession() → returns a user object or null so you can show different content.
  3. 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) call requireUser() 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

  1. Call auth.api.getSession({ headers: await headers() }) to retrieve the signed-in user.
  2. For POST, PUT, PATCH, or DELETE, run the CSRF guard:
    • assertOrigin(request)
    • assertFetchMetadata(request)
  3. Return responses with secureUserJson (success) or secureErrorJson (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, and userAction builders from convex/_helpers/builders.ts; they inject the authenticated user ID for you.
  • Apply withRateLimit for 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() or getCurrentSession() instead of reading cookies manually.
  • API routes combine auth.api.getSession() with CSRF checks.
  • Convex functions call the identity helpers.
  • Responses use secureUserJson / secureErrorJson.