--- type: Architecture Overview title: Authentication & authorization description: Google ID-token verification, the session cookie, and the admin/employee role split. tags: [architecture, auth, security] timestamp: 2026-08-12T00:00:00Z --- # Authentication & authorization ## Sign-in 1. The frontend loads the Google Identity Services script (`https://accounts.google.com/gsi/client`, in `frontend/index.html`) and renders the button in `frontend/src/auth/GoogleButton.tsx`. 2. Google returns a signed ID token to the browser (`credential`). The frontend does **not** trust it — it POSTs it to `POST /api/auth/google`. 3. The backend verifies the token server-side with `google-auth-library` (`backend/src/auth/google.ts`, `verifyGoogleIdToken`), checking the audience against `GOOGLE_CLIENT_ID` and requiring `email_verified`. ## Role resolution Two independent checks decide who gets in and as what role (`backend/src/routes/auth.ts`): - **Admin**: the verified email is in the `ADMIN_EMAILS` env var (comma-separated, lowercased — `backend/src/env.ts`). Admins do **not** need a row in the `employees` table. - **Employee**: the verified email matches an **active** row in [employees](/docs/data-model/employees.md). If neither matches, login is rejected with 403 ("This account is not registered as an EatMe employee"). This is how the employee whitelist is enforced — there's no self-service signup. ## Session On success the backend signs its own JWT (`backend/src/auth/session.ts`, `issueSession`) containing `{ email, name, role, employeeId }`, and sets it as an httpOnly, `SameSite=Lax` cookie (`eatme_session`, 12h TTL). All subsequent `/api/*` requests carry that cookie (`frontend/src/api/client.ts` sends `credentials: 'include'`); the backend never re-derives role from the DB on every request — it trusts the signed cookie until it expires. `backend/src/auth/middleware.ts` provides three guards used by the routers: - `requireAuth` — valid session cookie present - `requireAdmin` — `role === 'admin'` - `requireEmployee` — `employeeId != null` (blocks admins with no employee profile from hitting `/api/attendance/*`) ## Related - [System overview](./overview.md) - [Auth API](/docs/api/auth-routes.md) - [Employees table](/docs/data-model/employees.md)