A complete, production-ready authentication system built with the MERN stack featuring JWT-based authentication, Google OAuth 2.0 login, and email/password authentication with password reset functionality.
- ✅ Email/password authentication with signup
- ✅ Email verification (mandatory for account activation)
- ✅ Google OAuth 2.0 authentication
- ✅ Password reset via email (forgot password)
- ✅ JWT access tokens (15 min) + refresh tokens (7 days)
- ✅ Secure httpOnly cookies (NOT localStorage)
- ✅ Protected routes on frontend and backend
- ✅ Automatic token refresh
- ✅ User auto-creation on first login
- ✅ Modern React with Vite
- ✅ Express.js best practices
- ✅ Password hashing with bcrypt
- ✅ Cryptographically secure verification tokens
- Node.js + Express.js
- MongoDB with Mongoose
- Passport.js (Google OAuth 2.0)
- JWT (jsonwebtoken)
- bcrypt (password hashing)
- nodemailer (password reset emails)
- cookie-parser
- React 18 (Vite)
- React Router v6
- Axios
- Context API for state management
- Node.js (v18+)
- MongoDB (local or Atlas)
- Google Cloud Console project
- Go to Google Cloud Console
- Create a new project or select existing
- Enable Google+ API
- Go to Credentials → Create Credentials → OAuth 2.0 Client ID
- Configure OAuth consent screen
- Add Authorized redirect URIs:
http://localhost:5000/api/auth/google/callback - Copy Client ID and Client Secret
cd server
# Install dependencies
npm install
# Create .env file
cp .env.example .env
# Edit .env with your credentials:
# - MONGODB_URI
# - GOOGLE_CLIENT_ID
# - GOOGLE_CLIENT_SECRET
# - JWT_ACCESS_SECRET (use strong random string)
# - JWT_REFRESH_SECRET (use strong random string)
# Start development server
npm run devServer runs on http://localhost:5000
cd client
# Install dependencies
npm install
# Create .env file
cp .env.example .env
# Edit .env if needed (default: http://localhost:5000/api)
# Start development server
npm run devClient runs on http://localhost:5173
Option A: Local MongoDB
# Install MongoDB locally and start service
mongodOption B: MongoDB Atlas (Cloud)
- Create free cluster at MongoDB Atlas
- Get connection string
- Update
MONGODB_URIin server/.env
1. User clicks "Login with Google"
2. Frontend redirects to: /api/auth/google
3. Backend redirects to Google consent screen
4. User approves → Google redirects to: /api/auth/google/callback
5. Backend:
- Gets user profile from Google
- Finds or creates user in MongoDB
- Generates access token (15 min) + refresh token (7 days)
- Sets tokens in httpOnly cookies
- Redirects to frontend /dashboard
6. Frontend:
- Reads cookies automatically
- Fetches user data from /api/auth/me
- Stores user in Context
1. Frontend makes request with credentials (cookies auto-sent)
2. Backend auth middleware:
- Checks access token in cookies
- If valid → proceed
- If expired → check refresh token
- If refresh valid → issue new access token
- If both invalid → return 401
3. Frontend receives response or redirects to login
1. User clicks "Logout"
2. Frontend calls /api/auth/logout
3. Backend clears cookies
4. Frontend clears user state
5. Redirect to /login
| Method | Endpoint | Access | Description |
|---|---|---|---|
| GET | /api/auth/google |
Public | Initiate Google OAuth |
| GET | /api/auth/google/callback |
Public | OAuth callback |
| GET | /api/auth/me |
Protected | Get current user |
| POST | /api/auth/logout |
Protected | Logout user |
| GET | /api/auth/status |
Public | Check auth status |
✅ httpOnly Cookies - Prevents XSS attacks (tokens not accessible via JavaScript)
✅ SameSite Cookies - CSRF protection
✅ Secure Cookies - HTTPS only in production
✅ Short-lived Access Tokens - Limits damage if compromised
✅ Refresh Token Rotation - New access token from refresh
✅ Environment Variables - Secrets never in code
✅ CORS Configuration - Only allow trusted origins
- Navigate to
http://localhost:5173 - Click "Continue with Google"
- Login with Google account
- Should redirect to Dashboard
- Verify user info displays
- Click "Logout"
- Should redirect to Login page
- While logged out, try accessing:
http://localhost:5173/dashboard - Should redirect to
/login - Login again
- Try accessing
/loginwhile authenticated - Should redirect to
/dashboard
- Login and open DevTools → Application → Cookies
- Note the
accessToken(expires in 15 min) - Wait 15+ minutes or manually delete
accessToken - Make a request (refresh page)
- New
accessTokenshould be issued viarefreshToken
PORT=5000
MONGODB_URI=mongodb://localhost:27017/mern-auth
JWT_ACCESS_SECRET=<strong-random-string>
JWT_REFRESH_SECRET=<strong-random-string>
GOOGLE_CLIENT_ID=<your-client-id>
GOOGLE_CLIENT_SECRET=<your-client-secret>
CLIENT_URL=http://localhost:5173
NODE_ENV=developmentVITE_API_URL=http://localhost:5000/api- Set environment variables in hosting platform
- Update
GOOGLE_CLIENT_IDredirect URI to production URL - Set
NODE_ENV=production - Update
CLIENT_URLto production frontend URL
- Set
VITE_API_URLto production backend URL - Build:
npm run build - Deploy
dist/folder
- ✅ Use strong JWT secrets
- ✅ Enable HTTPS (secure cookies)
- ✅ Update CORS origins
- ✅ Use MongoDB Atlas or secure database
- ✅ Add rate limiting
- ✅ Implement request validation
- Ensure Google Console redirect URI matches exactly:
http://localhost:5000/api/auth/google/callback
- Check MongoDB is running:
mongod - Verify
MONGODB_URIin .env
- Check CORS
credentials: truein backend - Frontend axios must use
withCredentials: true - Same origin or proper CORS headers
- Check cookies are being sent with request
- Verify JWT secrets match in .env
- Check token expiry
- localStorage is vulnerable to XSS attacks
- httpOnly cookies cannot be accessed by JavaScript
- Browser automatically sends cookies with requests
- Access Token: Short-lived (15 min), used for API requests
- Refresh Token: Long-lived (7 days), used to get new access token
- Reduces risk: compromised access token expires quickly
- Passport simplifies OAuth flow
- Handles strategy pattern for multiple providers
- Standardized interface for authentication
This application implements mandatory email verification for all email/password registrations. Users cannot login until they verify their email address by clicking a link sent to their inbox.
- User signs up with email and password
- Account created with
isVerified: false - Verification token generated:
- 32-byte cryptographically secure random token
- SHA-256 hashed before storing in database
- Expires in 30 minutes
- Email sent with verification link
- User clicks link → account activated → auto-login
- Token invalidated after single use
- Random generation: Uses
crypto.randomBytes(32)for unpredictability - Hashed storage: SHA-256 hash stored in DB, not plaintext token
- Time-limited: 30-minute expiration window
- Single-use: Token deleted after successful verification
- Login attempts with unverified accounts return 403 Forbidden
- Clear error message: "Please verify your email before logging in"
- Prevents access to protected resources
- Google OAuth users are auto-verified (Google already verified the email)
- No verification email sent for OAuth registrations
NODE_ENV=development- Uses Ethereal Email (fake SMTP for testing)
- Verification link printed to console
- No real emails sent
NODE_ENV=production
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
EMAIL_FROM=noreply@yourapp.comRecommended SMTP Providers:
- Gmail SMTP (development, low volume)
- SendGrid (production, high deliverability)
- Mailgun (production, developer-friendly)
- AWS SES (production, cost-effective)
- Enable 2-Factor Authentication
- Generate App Password: https://myaccount.google.com/apppasswords
- Use app password in
SMTP_PASS
{
isVerified: { type: Boolean, default: false },
verificationToken: { type: String, select: false },
verificationTokenExpires: { type: Date, select: false }
}Response (Success):
{
"success": true,
"message": "Registration successful! Please check your email to verify your account.",
"requiresVerification": true
}Response (Success):
{
"success": true,
"message": "Email verified successfully! You are now logged in.",
"user": { "id": "...", "isVerified": true }
}After Registration:
- Shows message: "Check your email to verify your account"
- Verification link expires in 30 minutes
- In dev mode, link printed to server console
Login Attempt (Unverified):
- Error: "Please verify your email before logging in"
- User redirected to check email
✅ Secure token generation (crypto.randomBytes)
✅ Hashed token storage (SHA-256)
✅ Time-limited tokens (30 min expiration)
✅ Single-use tokens (deleted after verification)
✅ No token exposure (only sent via email)
✅ Clear error messages (user-friendly)
✅ Auto-login after verification (seamless UX)
✅ Production-ready email templates (HTML + plain text)