Files
eatme/docs/architecture/auth-flow.md
Michal Pemcak fffcb73ea4 Initial commit: EatMe attendance tracker
Google-SSO PWA for bistro employee clock-in/out, admin employee
management, and stats with CSV export. Express + SQLite backend,
React + Zustand frontend in the light-mono-tui design language.
Multi-stage Dockerfile, compose.yaml for image-based deploys, nginx
reverse-proxy template, and an OKF documentation bundle in docs/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 18:41:11 +02:00

59 lines
2.3 KiB
Markdown

---
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)