Auth and roles
How users sign in, how the JWT is issued and checked, and which roles can do what.
Every BabyMart client signs in the same way. The user proves who they are,
either with an email and password or through Google or GitHub. The API
then returns a signed JWT. The client stores the token and sends it as
Authorization: Bearer <token> with every protected request. The API reads
the user’s role from the database and decides what they may do.
Sign-in, two ways
Section titled “Sign-in, two ways”sequenceDiagram
autonumber
participant C as Client
participant F as Firebase Auth
participant A as API
participant DB as MongoDB
alt Email and password
C->>A: POST /api/auth/login
A->>DB: find user, compare bcrypt hash
else Google or GitHub
C->>F: signInWithPopup
F-->>C: Firebase ID token
C->>A: POST /api/auth/oauth with idToken
A->>A: verify with Google JWKS (jose)
A->>DB: find or create user
end
A-->>C: user + JWT (30 days)
C->>A: GET /api/auth/profile with Bearer token
POST/api/auth/login
POST/api/auth/register
POST/api/auth/oauth
GET/api/auth/profile
- Email. Passwords are stored as bcrypt hashes. Registering does not sign the user in on the storefront. They sign in afterwards.
- OAuth. The storefront and admin use Firebase only to open the
Google or GitHub popup. The API does not use
firebase-admin. It checks the Firebase ID token itself withjose, against Google’s public keys, with issuersecuretoken.google.com/<FIREBASE_PROJECT_ID>. Plain Google ID tokens are accepted too, when their audience is inGOOGLE_CLIENT_IDS. The mobile app sends its Google ID token this way. - Conflicts. If an email already belongs to an account with another
provider, the API answers
409and names the provider to use.
The token
Section titled “The token”| Algorithm | HS256, signed with JWT_SECRET (at least 32 characters in production) |
| Lifetime | 30 days (TOKEN_TTL) |
| Payload | { id, tv }, where tv is the user’s tokenVersion |
| Transport | Authorization: Bearer header only. The API never reads cookies |
| Revoking | Changing or resetting a password bumps tokenVersion, so older tokens stop working |
POST /api/auth/logout does nothing on the server, because there are no
sessions. Each client signs out by deleting its own copy of the token.
| Client | Stored in |
|---|---|
| Storefront | auth_token cookie (7 days, sameSite: lax). proxy.ts checks it on /user/* |
| Admin dashboard | localStorage auth-storage (zustand persist). A 401 clears it and sends the user to /login |
| Mobile app | AsyncStorage, on the persisted user-storage user object |
Every user has one role. Employees also have an employee_role.
| Role | Who | What they can do |
|---|---|---|
user |
Customer | Shop, check out, manage their own profile, addresses, cart, wishlist and orders |
vendor |
Seller | Manage their own store and products, once a admin approves them |
employee |
Staff | Work the order queues for their employee_role (see below) |
admin |
Owner | Everything, including users, catalog, content, salaries and purchases |
preview |
Demo visitor | Browse the admin dashboard read-only |
The API’s guards live in one file. An admin passes every guard, and an
incharge passes every employee guard.
Employee roles and the order workflow
Section titled “Employee roles and the order workflow”Fulfilment is split between staff. Each guard opens one part of
/api/orders/workflow or /api/cash-collections:
employee_role |
Can call |
|---|---|
call_center |
GET /call-center/queue, PUT /:id/confirm-address, PUT /:id/confirm-order |
packer |
GET /packer/queue, PUT /:id/pack |
incharge |
GET /delivery/queue, PUT /:id/assign-packer, PUT /:id/assign-deliveryman, plus everything below |
deliveryman |
GET /my-deliveries, PUT /:id/start-delivery, PUT /:id/collect-cod, PUT /:id/deliver, PUT /:id/return-cod, and submitting cash to accounts |
accounts |
GET /accounts/cod-pending, GET /accounts/completion-queue, PUT /:id/complete, and confirming cash receipts |
Paths in the table are relative to /api/orders/workflow. Analytics
(/api/analytics) is open to admin, accounts and incharge. The order
statuses these actions move through are shown in
How it works.
Read-only demo access
Section titled “Read-only demo access”Demo accounts can look around without changing anything. Two separate layers enforce this:
- API.
preventReadOnlyActionsblocksPOST,PUT,PATCHandDELETEfor the emailsdemo@gmail.comandtest@gmail.com. It responds with403and codeREAD_ONLY_USER. - Admin dashboard. An axios request interceptor cancels every mutating request from a user with role preview and shows a toast. The admin’s demo buttons sign in as that preview account.
Password reset
Section titled “Password reset”POST /api/auth/forgot-passwordcreates a random token, stores its SHA-256 hash with a one-hour expiry, and emails${CLIENT_URL}/auth/reset-password?token=….- The storefront page posts the token and the new password to
POST /api/auth/reset-password. - The password is replaced and
tokenVersionis bumped, which signs the user out on every device.
OAuth users can add a password later with POST /api/auth/set-password.