Authentication
The backend uses cookie-based JWT authentication with server-side session tracking for token revocation.
Sign-In Flow
POST /api/v1/auth/signin
Body: { email, password }
1. Find user by email in `users` collection
2. bcrypt.compare(password, user.password)
3. jwt.sign({ _id: user._id }, JWT_SECRET, { expiresIn: config.jwt.token_life })
4. Save token to `user_tokens` collection (with 7-day TTL index)
5. Set cookie: res.cookie('userAccessToken', token, cookieOptions)
6. Return: { success: true, user_details: { ... } }Cookie Options
| Option | Development | Production |
|---|---|---|
httpOnly | false | false |
secure | false | true |
sameSite | not set | 'none' |
domain | not set | .intecoglogistech.com |
maxAge | 7 days | 7 days |
Session Validation (authCheck)
Every protected request passes through middleware/auth.middleware.js:
const authCheck = async (req, res, next) => {
try {
// 1. Read token from cookie
const token = req.cookies['userAccessToken']
if (!token) throw new Error('No token')
// 2. Verify JWT signature + expiry
const decoded = jwt.verify(token, process.env.JWT_SECRET)
// 3. Server-side revocation check
const userToken = await UserToken.findOne({ token })
if (!userToken) throw new Error('Token revoked')
// 4. Load user record
const user = await User.findOne({ _id: decoded._id })
if (!user) throw new Error('User not found')
// 5. Attach to request
req.user = user
req.token = token
next()
} catch {
return res.status(401).json({ success: false, message: 'Please authenticate' })
}
}Middleware Guards
There are three authentication guards:
authCheck — Any authenticated user
Used on all user-facing protected routes. Validates JWT + token in user_tokens collection.
adminAuthCheck — Admin users only
Performs the same JWT + session validation, then additionally verifies:
if (user.type !== 'admin') {
throw new Error('User not an admin')
}Used on all /api/v1/admin/* routes.
deviceAuthCheck — IoT hardware token
Used only on /api/v1/devices/sensor-data. Validates a static Authorization header:
const token = req.headers['authorization']
if (token !== DEVICE_AUTH_TOKEN) {
return res.status(401).json({ ... })
}Security Note
The deviceAuthCheck uses a hardcoded static token string. For production security, this should be stored in an environment variable (DEVICE_AUTH_TOKEN).
Logout
POST /api/v1/user/logout
→ auth.middleware.js :: authCheck (validates session)
→ db.user.deleteToken(req.token) ← removes from user_tokens
→ Token is immediately invalid on all future requestsToken invalidation is immediate — no waiting for JWT expiry.
Token Storage (user_tokens collection)
// Schema
{
userId: ObjectId (ref: User),
token: String,
createdAt: Date (default: now), // TTL: auto-expires after 7 days
}The MongoDB TTL index on createdAt automatically purges expired tokens after 7 days, keeping the collection lean.
Password Reset Flow
POST /api/v1/auth/forgot-password
Body: { email }
→ Generate 32-byte hex token
→ Delete prior 'reset_password' verifications for email
→ Save Verification record (TTL: 30 min)
→ AWS SES: send forgot-password email with token link
POST /api/v1/auth/reset-password
Body: { email, password, token }
→ Find Verification record by { user_identification: email, token, type: 'reset_password' }
→ bcrypt.hash(password, saltRounds)
→ User.updateOne({ email }, { password: hashed })
→ Delete verification record
→ AWS SES: send confirmation emailEmail Verification Flow
POST /api/v1/auth/signup-send-verification
Body: { email }
→ Generate 32-byte hex token
→ Delete prior 'signup' verifications for email
→ Save Verification record (TTL: 30 min)
→ AWS SES: send verification email
POST /api/v1/auth/verify-signup
Body: { email, token }
→ Find matching Verification record
→ User.updateOne({ email }, { email_verified: true, email_verified_date: now })
→ Delete verification recordVerification records (in the verifications collection) have a 30-minute TTL — expired links stop working automatically.
