Admin routes
withAdmin protects admin-scoped API routes.
For each request, it:
- Loads the current session.
- Returns
401 Unauthorizedwhen there is no authenticated user. - Checks that
user.roleis set and returns403 Forbiddenwhen it is not. - Applies an optional permission check through
withPermission. - Passes the authenticated user and session to the route as
adminContext.
src/app/api/admin/with-admin.ts.
Customer routes
withCustomer protects organization-scoped API routes.
For each request, it:
- Loads the current session.
- Returns
401 Unauthorizedwhen there is no authenticated user. - Reads the active organization from the session.
- Verifies that the user is a member of that organization.
- Applies an optional organization permission check.
- Passes the user, session, membership, and trusted organization ID to the
route as
customerContext.
src/app/api/customer/with-customer.ts. The
organization ID it provides should be used to scope customer repository
queries.
The organization switcher updates activeOrganizationId on the Better Auth
session. Subsequent customer requests automatically resolve the new active
organization and its corresponding membership. See Portals for the
full portal-context model.
Permission checks
withPermission checks admin portal permissions through Better Auth. It accepts
the resource and actions required by a route, returns 403 Forbidden when the
check fails, and only calls the route handler when access is allowed.
Customer permission checks happen inside withCustomer because they also need
the active organization ID.
See Roles and permissions for the permissions assigned
to each portal role.
Error handling
Both portal wrappers composewithErrorHandler, which provides one error
boundary around the route.
The current handler catches ZodError and converts it into the standard
400 Bad Request validation response. Errors it does not recognize are
re-thrown so the framework can handle them instead of silently returning an
incorrect response.