Authentication
Pre-built: Login and register pages live at /login and /register. The API routes at /api/auth/login and /api/auth/register are wired up and ready.
Authentication uses JWTs issued on login/register and sent as a Bearer token on subsequent requests. There is no session store -- the token is the session. This keeps the stack stateless and works cleanly in serverless environments like Vercel.
How it works
- User registers or logs in via
POST /api/auth/registerorPOST /api/auth/login - The response body contains a
tokenstring - The client stores the token (localStorage or memory) and sends it as
Authorization: Bearer <token>on every authenticated request - API routes call
getUserFromRequest(req)to extract and verify the token
getUserFromRequest
import { getUserFromRequest } from '@/lib/auth';
export async function GET(req: Request) {
const user = await getUserFromRequest(req); // throws HttpError(401) if invalid
return Response.json({ user });
}getUserFromRequest throws HttpError(401) if the token is missing or invalid. The handleError helper in routes catches this and returns the correct status.
Protecting a route
import { getUserFromRequest } from '@/lib/auth';
import { handleError } from '@/lib/errors';
export async function POST(req: Request) {
try {
const user = await getUserFromRequest(req);
// user.id is available here
const body = await req.json();
// ... business logic
return Response.json({ ok: true });
} catch (error: unknown) {
return handleError(error);
}
}Passwords
Passwords are hashed with bcryptjs before storage. The plain password is never stored.
Always strip passwordHash before returning a user object:
const { passwordHash: _, ...safeUser } = user;
return Response.json({ user: safeUser });Advanced: Multi-tenant
When building multi-tenant features, the current organization is sent as X-Org-Id in the request header. Extract it alongside the JWT user:
const user = await getUserFromRequest(req);
const orgId = req.headers.get('X-Org-Id');Ownership and role checks happen in the service layer, not in routes.