A production-grade authentication system implementing JWT access tokens and HTTP-only refresh token cookies with MongoDB storage.
- Features
- Tech Stack
- System Flow Diagram
- API Endpoints
- Installation
- Environment Variables
- Project Structure
- Security Features
- Authentication Flow
- Error Handling
- Frontend Integration Guide
- JWT Access Tokens - Short-lived (15 min) tokens sent in response body
- HTTP-Only Refresh Tokens - Long-lived (7 days) tokens stored in secure cookies
- MongoDB Storage - Refresh tokens hashed and stored with user reference
- Password Hashing - bcrypt for secure password storage
- Token Expiry Management - Automatic cleanup using MongoDB TTL indexes
- Security Headers - Helmet.js for HTTP security headers
- Rate Limiting - Prevent brute force attacks
- Session Management - Multiple device sessions supported
- Logout Functionality - Token revocation on logout
- Node.js - Runtime environment
- Express.js - Web framework
- MongoDB - Database
- Mongoose - ODM for MongoDB
{
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.5.0",
"bcrypt": "^5.1.1",
"jsonwebtoken": "^9.0.2",
"cookie-parser": "^1.4.6",
"dotenv": "^16.3.1",
"helmet": "^7.0.0",
"express-rate-limit": "^6.10.0"
}
}βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β Client ββββββΆβ /signup ββββββΆβ MongoDB β
β (Frontend)β β /login β β - Users β
βββββββββββββββ ββββββββββββββββ β - Refresh β
β² β β Tokens β
β β βββββββββββββββ
β ββββββββΌβββββββ β
β β Generate β β
β β Tokens: ββββββββββββββββ
β β β’ Access β
β β β’ Refresh β
β ββββββββ¬βββββββ
β β
β ββββββββΌβββββββ
βββββββββββββ Response: β
β β’ Access in β
β Body β
β β’ Refresh inβ
β Cookie β
βββββββββββββββ
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β Request ββββββΆβ Authenticate ββββββΆβ Return β
β /profile β β Middleware β β User Data β
β + Access β β β’ Verify JWT β β β
β Token β β β’ Check exp β β β
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β Access TokenββββββΆβ /refresh ββββββΆβ Verify β
β Expired β β + Cookie β β Refresh β
βββββββββββββββ β (auto-sent) β β Token in β
ββββββββββββββββ β Database β
β ββββββββ¬βββββββ
β β
βββββββββΌββββββββ ββββββββΌβββββββ
β Generate New β β Check β
β Access Token ββββββ Expiry & β
βββββββββ¬ββββββββ β User β
β βββββββββββββββ
βββββββββΌββββββββ
β Return New β
β Access Token β
βββββββββββββββββ
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β /logout ββββββΆβ Delete Token ββββββΆβ Clear β
β + Cookie β β from DB β β Cookie β
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
Request Body:
{
"firstName": "John",
"lastName": "Doe",
"emailId": "john@example.com",
"password": "password123"
}Response:
{
"message": "User created successfully",
"accessToken": "eyJhbGc...",
"user": {
"id": "650f1a2b3c4d5e6f7a8b9c0d",
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com"
}
}Sets Cookie: refreshToken (HTTP-only, 7 days)
Request Body:
{
"emailId": "john@example.com",
"password": "password123"
}Response:
{
"message": "Login successful",
"accessToken": "eyJhbGc...",
"user": {
"id": "650f1a2b3c4d5e6f7a8b9c0d",
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com"
}
}Sets Cookie: refreshToken (HTTP-only, 7 days)
Headers:
Authorization: Bearer <access_token>
Response:
{
"user": {
"_id": "650f1a2b3c4d5e6f7a8b9c0d",
"firstName": "John",
"lastName": "Doe",
"emailId": "john@example.com",
"createdAt": "2023-09-22T10:30:00.000Z",
"updatedAt": "2023-09-22T10:30:00.000Z"
}
}Request: (Cookie automatically sent)
Cookie: refreshToken=<token>
Response:
{
"accessToken": "eyJhbGc..."
}Request: (Cookie automatically sent)
Cookie: refreshToken=<token>
Response:
{
"message": "Logged out successfully"
}Clears Cookie: refreshToken
- Clone the repository
git clone <repository-url>
cd auth-system- Install dependencies
npm install- Create .env file
cp .env.example .env- Start MongoDB
# Local MongoDB
mongod
# Or use MongoDB Atlas (cloud)- Run the server
# Development
npm run dev
# Production
npm start- bcrypt hashing with salt rounds (10)
- Passwords never stored in plain text
- Access tokens: JWT with 15-minute expiry
- Refresh tokens: Cryptographically random (320 bits), HTTP-only cookies
- Refresh tokens hashed in database (SHA-256)
- Automatic cleanup via MongoDB TTL indexes
- Helmet.js for security headers
- CORS protection
- XSS protection
- HSTS in production
- Rate limiting (100 requests per 15 minutes)
- Input validation (basic checks)
- Secure cookie flags (HTTP-only, Secure, SameSite)
- Tokens hashed before storage
- No plain-text secrets
- Indexed for performance
- Client sends credentials
- Server validates and creates/verifies user
- Generates access token (JWT) and refresh token (random)
- Stores hashed refresh token in MongoDB with user reference
- Sets refresh token as HTTP-only cookie
- Sends access token in response body
- Client includes access token in Authorization header
- Middleware verifies JWT signature and expiry
- If valid, attaches user to request and proceeds
- If expired, client should call refresh endpoint
- Client calls
/refreshendpoint (cookie auto-sent) - Server hashes cookie value and looks up in database
- Verifies token exists and not expired
- Generates new access token
- Returns new access token (same refresh token remains)
- Client calls
/logoutendpoint - Server hashes refresh token from cookie
- Deletes matching token from database
- Clears refresh token cookie
- Client discards access token
- 200 - Success
- 201 - Resource created
- 400 - Bad request (missing fields)
- 401 - Unauthorized (missing/invalid token)
- 403 - Forbidden (token expired/invalid)
- 404 - Resource not found
- 409 - Conflict (email already exists)
- 500 - Server error
{ "message": "Email already registered" }
{ "message": "Invalid email or password" }
{ "message": "Access token missing" }
{ "message": "Invalid or expired access token" }
{ "message": "Refresh token missing" }
{ "message": "Invalid refresh token" }
{ "message": "Refresh token expired" }// In memory (recommended)
let accessToken = null;
// After login/signup
accessToken = response.data.accessToken;fetch('/api/auth/profile', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});const handleRequest = async () => {
try {
const response = await fetch('/api/protected', {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
if (response.status === 401) {
// Try to refresh
const refreshResponse = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'include' // Important: send cookies
});
if (refreshResponse.ok) {
const { accessToken: newToken } = await refreshResponse.json();
accessToken = newToken;
// Retry original request
} else {
// Redirect to login
window.location.href = '/login';
}
}
} catch (error) {
console.error('Request failed:', error);
}
};const logout = async () => {
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include'
});
accessToken = null;
window.location.href = '/login';
};// On app start, try to refresh
const initializeAuth = async () => {
try {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'include'
});
if (response.ok) {
const { accessToken: newToken } = await response.json();
accessToken = newToken;
return true;
}
} catch (error) {
console.error('Auto-login failed:', error);
}
return false;
};- Token rotation for refresh tokens
- Device fingerprinting
- Email verification
- Password reset flow
- Role-based access control (RBAC)
- Session management dashboard
- Two-factor authentication (2FA)
- OAuth2 integration
MIT
Note: This is a production-grade implementation but always conduct security audits and penetration testing before deploying to production with real user data.