Skip to main content

Middleware (proxy.ts)

apps/web/src/proxy.ts is the Next.js middleware file (renamed from middleware.ts — Next.js 16 uses proxy.ts as the convention). It runs on every matched request before the page is rendered.

What It Does

  1. Generates a request IDx-request-id header (10-char UUID without dashes) for log correlation across the frontend and API
  2. Detects session presence — reads the session cookie without validating it
  3. Routes staff subdomain requests — redirects and rewrites for staff.* subdomains
  4. Routes main domain requests — redirects between portal, login, and staff pages based on session state

The middleware detects session presence by checking for the Better Auth session cookie:

// HTTPS (production)
const hasSession = request.cookies.has("__Secure-better-auth.session_token");

// HTTP (local dev)
const hasSession = request.cookies.has("better-auth.session_token");

This is a presence check only — the middleware does not validate or decode the cookie. An expired or tampered cookie would pass this check. The middleware is a routing hint, not a security gate.

Actual session validation happens API-side on every authenticated request via SessionGuard.


Staff Subdomain Routing

Detected by hostname prefix:

const isStaff = hostname.startsWith("staff.") || hostname.startsWith("staff-staging.");

For staff subdomain requests:

  • / → rewrite to /staff/portal (not a redirect — URL stays as /)
  • /login, /register, /staff/login, /staff/first-login/* → allowed through (no auth required)
  • Any other path without session → redirect to /staff/login
  • Any other path with session → allowed through

Main Domain Routing

For requests on the main domain (pcmr.gr, localhost, etc.):

ConditionAction
Has session, visiting /login or /registerRedirect to /portal
Has session, visiting /staff/portal/*Redirect to /portal (wrong portal)
No session, visiting /portal/*Redirect to /login
No session, visiting /staff/portal/*Redirect to /staff/login
Anything elsePass through

Matcher Config

export const config = {
matcher: [
"/portal/:path*",
"/staff/:path*",
"/login",
"/register",
"/((?!_next/static|_next/image|favicon.ico|images|fonts|icons).*)",
],
};

The catch-all excludes static assets (_next/static, _next/image, favicon.ico, images/, fonts/, icons/).


Request ID Propagation

Every request gets an x-request-id header:

const requestId = crypto.randomUUID().replace(/-/g, "").substring(0, 10);
const headers = new Headers(request.headers);
headers.set("x-request-id", requestId);

This ID is:

  • Forwarded to NestJS by the proxy route handlers
  • Logged by Pino (appears in Loki as requestId)
  • Available in browser DevTools for tracing a specific request across frontend + API logs

Why This Is Not a Security Gate

The middleware runs in the Next.js Edge Runtime and cannot:

  • Verify JWT signatures
  • Query the database
  • Call the API with a timeout guarantee

Its role is purely UX — fast redirects to avoid flash of wrong content. Security is enforced at the API layer by SessionGuard.

Do not add security logic (role checks, permission checks) to the middleware. Add it to API endpoints and use getServerSession() in server components.