⭐ A production‑style full‑stack authentication system demonstrating secure user workflows, media uploads, and real‑world backend architecture.
A full‑stack authentication and user dashboard application built with Node.js (Express), MongoDB, and React. This project implements secure authentication flows, CAPTCHA protection, Cloudinary image upload, interactive maps, and third‑party API integrations used in real‑world web applications.
- Overview
- Deployment
- Why This Project Exists
- Usage Example
- Architecture
- Features
- Tech Stack
- Environment Variables
- Project Structure
- Authentication Flows
- Testing
- API Reference
- License
This application implements a secure authentication system with:
- User signup with email verification (6-digit code, 10-minute TTL)
- CAPTCHA‑protected login
- Password reset via email verification code
- Profile image upload with Cloudinary CDN storage
- Protected user dashboard with profile management
- Country selection with live map preview
- Interactive map that auto-zooms to selected country
The goal is to simulate a realistic, production‑ready authentication architecture with modern media handling capabilities.
🚀 Live Production Environment: https://authentication-user-dashboard-app.onrender.com
This application is fully decoupled and distributed on Render:
- Frontend: Hosted as a managed Static Site serving the Vite production build.
- Backend: Hosted as a Web Service running Node.js, connected to a MongoDB Atlas database cluster.
- Media Storage: User-uploaded profile images are stored and served via Cloudinary CDN.
This project is a portfolio centerpiece showcasing full‑stack capabilities:
| Area | What I Demonstrated |
|---|---|
| Backend | Express REST API, JWT auth, bcrypt hashing, Brevo email, Zod validation, Multer file handling |
| Frontend | React 19 components, React Router, Axios interceptors, Bootstrap, Leaflet map embed, FileReader API |
| Security | HttpOnly cookies, double-submit CSRF, reCAPTCHA v2, bcrypt-hashed time-limited codes, Zod input validation, Helmet headers |
| Media Management | Cloudinary CDN integration, profile picture upload/delete, image optimization, Multer middleware |
| Integrations | REST Countries API, Google reCAPTCHA, Brevo email API, Cloudinary |
| Database | MongoDB schema design, Mongoose ODM, user data persistence |
| DevOps | Decoupled cross-origin cloud deployment, CORS configuration, Redis session + rate-limit store |
- Sign up – Enter your details, upload a profile picture (optional), and choose a country — the map auto‑zooms to your selection.
- Verify your email – A 6-digit code is sent to your inbox. Enter it to activate your account and get logged in immediately.
- Log in – Solve the reCAPTCHA, then receive a secure session via
httpOnlycookie. - Dashboard – View your complete profile including email, username, country, join date, and profile image.
- Reset your password – Request a 6‑digit verification code by email, verify it, then set a new password.
- Update profile – Change your username, country, or profile picture (upload new or delete existing).
flowchart TB
subgraph Client["🖥️ React Client (Render Static Site)"]
direction TB
A["Login / Signup / Dashboard Pages"]
B["Axios HTTP Client + CSRF Interceptor"]
A2["ProfileImageUploader Component<br/>• FileReader Preview<br/>• Hover Effects"]
end
subgraph Server["⚙️ Express Server (Render Web Service)"]
direction TB
C["🌐 API Routes<br/>• /api/user<br/>• /api/country<br/>• /api/codeRequest<br/>• /api/profile"]
D["🔐 Authentication Middleware<br/>• JWT via HttpOnly Cookie<br/>• bcrypt Hashing"]
E["✅ Validation Layer<br/>• reCAPTCHA Verification<br/>• Zod Schema Validation"]
F["📧 Email Service<br/>• Brevo API<br/>• 6-digit code generator"]
G["🖼️ Media Handler<br/>• Multer MemoryStorage<br/>• Cloudinary Uploader"]
H["🛡️ Security Layer<br/>• CSRF double-submit<br/>• Redis rate limiting<br/>• Helmet headers"]
end
subgraph Data["🗄️ Data Layer"]
direction TB
I[("MongoDB Atlas<br/>Users Collection")]
J[("Redis<br/>Sessions + Rate limits")]
K[("Cloudinary CDN<br/>Profile Images")]
end
subgraph External["🌐 External APIs"]
L["Google reCAPTCHA"]
M["REST Countries API"]
N["Brevo Email API"]
end
Client -->|HTTP Requests with Credentials| Server
Server -->|Read/Write| I
Server -->|Sessions/Rate limits| J
Server -->|Upload/Delete| K
Server -->|Verify token| L
Server -->|Fetch countries| M
Server -->|Send emails| N
style Client fill:#6366F1,color:#fff,stroke:#333,stroke-width:1px
style Server fill:#F6821F,color:#fff,stroke:#333,stroke-width:3px
style Data fill:#2DBA4E,color:#fff,stroke:#333,stroke-width:1px
style External fill:#F38020,color:#fff,stroke:#333,stroke-width:1px
- HttpOnly Cookie Sessions – JWT stored exclusively in httpOnly cookies; never exposed to JavaScript.
- Double-Submit CSRF Protection – Every mutating request requires a valid CSRF token. The Axios client auto-fetches and caches the token, and retries automatically on a 403.
- Email Verification – 6-digit code sent via Brevo API on signup. Codes are bcrypt-hashed server-side and expire after 10 minutes.
- CAPTCHA Protection – Google reCAPTCHA v2 on login to prevent automated brute-force attempts.
- Redis-Backed Rate Limiting – Auth endpoints limited to 10 failed attempts per 15 minutes; email endpoints to 3 requests per hour. Counts only fail, not success.
- Server‑Side Validation – All inputs validated with Zod schemas before hitting the database.
- Separate Reset JWT Secret – Password reset tokens are signed with a dedicated
JWT_SECRET_RESET_PASSWORD, isolated from the session secret.
- Password Reset Flow – Time‑limited 6‑digit codes, bcrypt-hashed, sent via Brevo email API.
- Interactive Dashboard – Dynamic profile fetching with loading states and graceful error handling.
- Profile Image Upload – Upload, preview (with FileReader), and delete profile pictures stored on Cloudinary CDN.
- Default Avatar Generation – UI Avatars API fallback when no custom image is uploaded.
- Country Integration – Dynamic country list from REST Countries API, including flags.
- Live Map Preview – Leaflet map that auto-zooms to selected country coordinates.
- Async Coordinate Fetching – Country selection triggers background API call to update map viewport.
- Polished Image Uploader – Hover effects with SVG overlay, smooth transitions, and click-to-upload.
- Responsive Design – Bootstrap-powered layouts for desktop and mobile.
- Loading States – Spinners and disabled buttons during async operations.
- Toast Notifications – Success and error feedback via react-toastify.
| Layer | Technologies |
|---|---|
| Frontend | React 19, Vite 7, React Bootstrap, React Router 7, Axios, Leaflet, FileReader API |
| Backend | Node.js, Express 5, MongoDB (Mongoose 9), JWT, bcrypt, Zod, Multer |
| Brevo API | |
| Media | Cloudinary CDN (upload, storage, auto-optimization) |
| Security | Google reCAPTCHA v2, httpOnly cookies, CSRF double-submit, Redis rate limiting, Helmet |
| Session Store | Redis (via connect-redis + ioredis) |
| APIs | REST Countries API, Google reCAPTCHA API, Brevo API, Cloudinary API |
MONGODB_URI=mongodb+srv://...
JWT_SECRET=your_jwt_secret
JWT_SECRET_RESET_PASSWORD=your_reset_secret
SESSION_SECRET=your_session_secret
CSRF_SECRET=your_csrf_secret
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
CAPTCHA_SECRET=your_recaptcha_secret
BREVO_API_KEY=your_brevo_key
BREVO_FROM_NAME=Your App Name
BREVO_FROM_EMAIL=noreply@yourdomain.com
PORT=3000
NODE_ENV=development| Variable | Description |
|---|---|
MONGODB_URI |
MongoDB Atlas connection string |
JWT_SECRET |
Secret for signing session JWTs |
JWT_SECRET_RESET_PASSWORD |
Separate secret for password reset tokens |
SESSION_SECRET |
Express session secret |
CSRF_SECRET |
Secret for CSRF double-submit token generation |
CLOUDINARY_CLOUD_NAME |
Cloudinary cloud name |
CLOUDINARY_API_KEY |
Cloudinary API key |
CLOUDINARY_API_SECRET |
Cloudinary API secret |
CAPTCHA_SECRET |
Google reCAPTCHA server-side secret key |
BREVO_API_KEY |
Brevo transactional email API key |
BREVO_FROM_NAME |
Sender display name for outgoing emails |
BREVO_FROM_EMAIL |
Sender email address for outgoing emails |
PORT |
Server port (defaults to 3000) |
NODE_ENV |
Set to production to enable secure cookies and rate limiting |
VITE_API_URL=https://your-backend.onrender.com/api
VITE_REACT_APP_RECAPTCHA_SITE_KEY=your_recaptcha_site_key
VITE_GOOGLE_MAPS_API_KEY=your_google_maps_key| Variable | Description |
|---|---|
VITE_API_URL |
Base URL of the backend API |
VITE_REACT_APP_RECAPTCHA_SITE_KEY |
Google reCAPTCHA public site key |
VITE_GOOGLE_MAPS_API_KEY |
Google Maps JavaScript API key |
├── client/ # React frontend
│ ├── src/
│ │ ├── api/
│ │ │ ├── apiClient.js # Axios instance with CSRF interceptor + auto-retry
│ │ │ ├── userApi.js # User CRUD operations
│ │ │ ├── countryApi.js # Country data fetching
│ │ │ └── reqCodeApi.js # Verification code + password reset endpoints
│ │ ├── components/
│ │ │ ├── ProfileImageUploader.jsx
│ │ │ ├── CountrySelector.jsx
│ │ │ ├── AccountFields.jsx
│ │ │ ├── SignupMap.jsx
│ │ │ ├── RecaptchaComponent.jsx
│ │ │ ├── Login.jsx
│ │ │ ├── Signup.jsx
│ │ │ ├── ResetPassword.jsx
│ │ │ ├── VerifyCard.jsx
│ │ │ ├── ConfirmationModal.jsx
│ │ │ └── pageContainer.jsx
│ │ ├── helper/
│ │ │ └── AuthContext.jsx # Auth state context provider
│ │ ├── pages/
│ │ │ ├── loginPage.jsx
│ │ │ ├── signupPage.jsx
│ │ │ ├── homePage.jsx
│ │ │ ├── forgotPasswordPage.jsx
│ │ │ ├── verifyEmailPage.jsx
│ │ │ └── resetPasswordPage.jsx
│ │ ├── utils/
│ │ │ └── authUtils.js
│ │ ├── App.jsx
│ │ └── main.jsx
│ └── package.json
│
├── server/
│ ├── config/
│ │ ├── cloudinary.js # Cloudinary upload/delete helpers
│ │ └── redis.js # ioredis client
│ ├── controllers/
│ │ └── emailSender.js # Brevo email integration
│ ├── middleware/
│ │ ├── auth.js # JWT verification (cookie + Bearer header fallback)
│ │ ├── csrf.js # Double-submit CSRF setup
│ │ └── rateLimiter.js # Redis-backed rate limiters
│ ├── models/
│ │ ├── user.js # Mongoose user schema
│ │ └── userZSchema.js # Zod validation schema
│ ├── routes/
│ │ ├── user.js # Auth, profile management
│ │ ├── country.js # Country list, flags, coordinates
│ │ ├── codeRequest.js # Email verification + password reset
│ │ └── profileImage.js # Cloudinary upload/delete
│ └── index.js # Server entry point
│
├── playwright/
│ ├── helpers/
│ │ └── recycle.ts # Reusable flows: login, signup form fill
│ └── utils/
│ └── functions.ts # Auth-state persistence, Mailpit email lookup helpers
│
├── tests/
│ ├── 01-createAccount.spec.ts # Signup, email verification, and input-validation edge cases
│ ├── 02-authAndSecurity.spec.ts # Session/cookie tampering, expired sessions, invalid CSRF
│ ├── 03-CRUDActionsAndNavigation.spec.ts # Profile edit, logout, login, password reset, account deletion
│ └── 04-unverifiedEmailCRUDActions.spec.ts # Access control for unverified accounts
│
├── playwright.config.js # Cross-browser (Chromium + Firefox) parallel test config
└── README.md
- User enters email, username, password, and selects a country. Optionally uploads a profile picture.
- On submit, the client fetches a CSRF token, then posts registration data to
/api/user/storeRegistrationData. This creates an unverified placeholder user in MongoDB and stores a bcrypt-hashed 6-digit code with a 10-minute TTL. - A verification code is sent to the user's email via Brevo.
- User enters the code on the verification screen. Server validates the bcrypt hash and, on success, flips
email_verifiedtotrue, issues a session JWT via httpOnly cookie, and returns the user object. The user is immediately logged in. - If a profile image was selected during signup, it is uploaded to Cloudinary via the temporary-user image endpoint.
- User submits email, password, and reCAPTCHA token.
- Server validates the reCAPTCHA with Google's API.
- Unverified accounts (
email_verified: false) are rejected with a 403. - Password compared against the bcrypt hash in MongoDB.
- On success, a JWT is signed and stored in an httpOnly, SameSite=None cookie. No token is stored in JavaScript-accessible storage.
All mutating requests (POST, PUT, DELETE) require a valid x-csrf-token header. The Axios client in apiClient.js caches the CSRF token for 4 minutes and refreshes it automatically. If a request returns 403, the interceptor forces a token refresh and retries the request once transparently.
- User requests a reset with their email address.
- Server generates a 6-digit code, bcrypt-hashes it, stores it with a 10-minute TTL, and sends it via Brevo.
- User submits the code. Server validates the hash and issues a short-lived JWT signed with
JWT_SECRET_RESET_PASSWORD(15-minute expiry). - User sets a new password. Server verifies the reset token, bcrypt-hashes the new password, and saves it. The verification code is cleared.
The app is covered by a Playwright end-to-end suite that runs against a live deployment rather than mocks, exercising the real signup → verify → login → dashboard → reset → delete lifecycle across Chromium and Firefox in parallel.
| Suite | Coverage |
|---|---|
01-createAccount.spec.ts |
Full signup + email verification happy path, plus negative cases: duplicate email, mismatched passwords, invalid email format, weak password, wrong-format profile picture upload, unverified abandonment |
02-authAndSecurity.spec.ts |
Requests with no cookies, a tampered JWT, an expired session, and an invalid CSRF token are all rejected as expected |
03-CRUDActionsAndNavigation.spec.ts |
Authenticated navigation, profile editing, logout, login with bad credentials, password reset (valid + invalid codes), login with verified credentials, and account deletion |
04-unverifiedEmailCRUDActions.spec.ts |
Confirms unverified accounts are correctly blocked from logging in and runs the clean-up script at the end to avoid cluttering the db |
Supporting infrastructure:
- Real email verification — tests send and read actual verification codes via a self-hosted Mailpit instance rather than stubbing the email step out.
- Auth-state persistence (
playwright/utils/functions.ts) — reusable helpers for saving/restoring per-browser auth state and looking up per-project test email addresses, so verified sessions can be reused across specs instead of re-registering every test. - Reusable flows (
playwright/helpers/recycle.ts) — sharedloginUserandfillUpSignUpFormfunctions, including reCAPTCHA handling, so each spec stays focused on what it's actually testing. CI pipeline (.github/workflows/tests-automation.yml): pushes to thetestingbranch trigger a GitHub Actions run that warms the Render instance, runs the full suite, and posts a formatted pass/fail summary (with a Mermaid pie chart) to the workflow run. On success, it automatically opens a PR mergingtestingintomain— somainonly ever receives changes that have passed the full E2E suite.
See API.md for full endpoint documentation including request/response formats, auth requirements, and error codes.
This project is licensed under the MIT License. See the LICENSE file for details.
Built by Mel000000
