Skip to content

Authentication

  • Admin: POST /api/v1/auth/admin/login, POST /api/v1/auth/admin/refresh, and related admin-only routes (see Swagger under auth and admin tags).
  • Vendor: auth/vendor login/refresh, plus /api/v1/auth/vendor/context — JWT-only bootstrap routes so employees can load POS/store context even when RBAC modules are narrow (see comment on VENDOR_AUTH_CONTEXT_API in apps/vendor-web/src/lib/api.ts).
  • Customer / shared: other auth/* controllers as listed in Swagger.

Exact request/response shapes live in OpenAPI — do not treat this page as a schema reference.

  • Base URL: both admin-web and vendor-web use VITE_API_URL when set to a non-empty string; in import.meta.env.DEV with no URL they default to http://localhost:3000; in production builds with an empty VITE_API_URL, the base is "" so Axios uses same-origin relative URLs to /api/v1 (matches Docker build). See getApiBaseUrl() in each app’s src/lib/api.ts.
  • Access token: request interceptor sets Authorization: Bearer <access> from the Zustand auth store.
  • Refresh: on 401, clients call POST /api/v1/auth/admin/refresh or POST /api/v1/auth/vendor/refresh with JSON body { "refresh_token": "<refresh>" }, then persist the new pair. A single-flight promise (refreshAccessPromise) avoids concurrent refresh stampedes.

Defaults in apps/backend/.env.example (override in each environment):

VariableDefaultRole
JWT_ACCESS_EXPIRES15mShort-lived access JWT
JWT_REFRESH_EXPIRES7dRefresh token lifetime

Secrets: JWT_ACCESS_SECRET, JWT_REFRESH_SECRET — use long random values in production.

Rotation is refresh-on-401 (no sliding refresh on a timer in the snippet above). OTP and password-reset shapes are defined in OpenAPI under the auth tags.

Three-step flow (same pattern for admin and vendor); base path is /api/v1 plus auth/admin or auth/vendor:

StepMethod & pathBody (JSON)Notes
1. Request codePOST …/password-reset{ "email", "admin_type" | "vendor_type" }Response is always generic success (anti–email-enumeration). If the account exists, a 6-digit OTP is stored server-side.
2. Verify OTPPOST …/password-reset/verify{ "email", "admin_type" | "vendor_type", "code" }Returns { "success", "reset_token" } — short-lived JWT signed with JWT_ACCESS_SECRET.
3. CompletePOST …/password-reset/complete{ "reset_token", "password" } (password min 8)Updates the hashed password for that user.

Operational notes

  • OTP storage is in-memory in PasswordResetOtpService (single-instance / dev friendly). For multiple API instances, replace with Redis (or similar).
  • PASSWORD_RESET_LOG_OTP: set to 1 to always log the plaintext OTP; 0 to disable. If unset, OTP is logged when NODE_ENV !== 'production' (see service implementation).
  • SPAs: call these endpoints with skipAuthHandler: true on the Axios config so a 401 on a bad OTP does not trigger the refresh-token flow.
  • Admin: payload includes modules (see Admin panel overview); backend guards enforce module rules on services.
  • Vendor: profile after login includes vendor_type, modules, and optional module_permissions for employees; owners bypass module checks in the SPA.