Every implemented auth feature explained: registration, email/password login, Google OAuth, logout, session cookies, refresh rotation, email verification, 6-digit OTP reset, and the admin role — plus rate limits verified from the actual route files.
httpOnly; secure + SameSite=strict in production, lax in development
refreshToken (refresh JWT)
JWT_REFRESH_EXPIRES_IN — default 30d
as above
oauth_state / oauth_code_verifier
10 minutes
set only during the Google OAuth handshake
The browser never sees token contents (httpOnly). The refresh token is additionally stored HASHED in the tokens collection; on refresh the old row is revoked and a fresh token pair is issued — reuse of an old refresh token is detected and revokes the family. Non-browser clients may instead send Authorization: Bearer <accessToken> headers.
Registration — name/email/password(+phone) with Joi validation; password hashed with bcrypt; a verification email is queued (token link valid via GET /auth/verify-email/:token).
Email + password login — issues the cookie pair; unverified accounts can still sign in but expose a "verify your email" state in the UI.
Email verification — the link endpoint flips isEmailVerified; the "resend-verification" endpoint requests a fresh link (rate-limited).
Google OAuth login & account linking — see the Google OAuth guide; link/unlink endpoints require a session.
OTP password reset — see below.
Logout — clears both cookies and revokes stored refresh rows.
Refresh rotation — POST /auth/refresh renews the access token from a valid refresh cookie.
Profile management — GET/PATCH profile, change password, deactivate account, hard delete — all require an authenticated session.
User submits the email → backend generates a 6-digit code (100000–999999), stores only its SHA-256 hash (5-minute expiry, single use), and emails it.
In development the code is printed in the backend console and returned in the API response as data.resetOtp (the UI shows a convenient banner with it). In production both are disabled — only the email carries the code.
POST /auth/verify-otp confirms the code (rate-limited); POST /auth/reset-password consumes the code, sets the new bcrypt-hashed password, and revokes every existing refresh token so old sessions die after a reset.
Reset endpoints (request shape)
POST /api/v1/auth/forgot-password { "email": "user@example.com" }
POST /api/v1/auth/verify-otp { "email": "user@example.com", "otp": "123456" }
POST /api/v1/auth/reset-password { "email": "user@example.com", "otp": "123456", "password": "NewStrongPass1!" }
3 per 15 min (configurable via RATE_LIMIT_FORGOT_* env vars)
IP + email
OTP verify limiter
POST /auth/verify-otp, POST /auth/reset-password
5 per 15 min (configurable via RATE_LIMIT_OTP_* env vars)
IP + email
The forgot/OTP limiters key by IP + email, so two different users behind an office network do not block each other — but a bot hammering one address hits the IP part as well.
Backend: /auth/me, the whole /users/* group and /admin/* use the protect middleware, which accepts the cookie pair or a Bearer header and injects the user. Frontend: dashboard/admin pages are guarded client-side; authenticated state comes from the cookie session via /auth/me.
Users carry a role field (customer by default); only role=admin may call /admin/*. The kit provides ONE safe way to create an admin — backend/seed-admin.js. It connects with the MONGO_URI from your environment, looks up the user by ADMIN_EMAIL, and either creates a new admin account or promotes the existing user with that address to admin. It prints the resulting password to the console.
backend — seed the admin
# In backend/.env, set these BEFORE running:
# ADMIN_EMAIL=you@yourdomain.com
# ADMIN_PASSWORD=<a strong password of your own>
# ADMIN_NAME=Your Name
node seed-admin.js
# → Admin user created successfully / Updated existing user to admin role
There is no public endpoint that changes roles — good. Do not attempt to edit arbitrary database fields via mongo shell without understanding the schema; the supported path is always the seed script for the first admin, and the admin panel (PATCH /admin/users/:id) for the rest of user management.