This project is a simple Node.js + Express + MongoDB authentication backend.
If you are learning auth for the first time, this project teaches:
- how user registration and login works
- how passwords are hashed before saving
- how
accessTokenandrefreshTokenare used together - how refresh token rotation works with sessions
- how logout from one device and all devices works
- Node.js
- Express
- MongoDB + Mongoose
- JWT (
jsonwebtoken) cookie-parsermorgandotenv- Node
cryptomodule
Auth2/
├─ server.js
├─ package.json
├─ .env
├─ src/
│ ├─ app.js
│ ├─ config/
│ │ ├─ config.js
│ │ └─ database.js
│ ├─ models/
│ │ ├─ user.model.js
│ │ └─ session.model.js
│ ├─ controllers/
│ │ └─ auth.controller.js
│ └─ routes/
│ └─ users.route.js- imports Express app from
src/app.js - imports DB connection from
src/config/database.js - calls
connectDB() - starts server on
PORT(or5000fallback)
- creates Express app
- enables JSON body parsing with
express.json() - enables logs with
morgan("dev") - enables cookie reading with
cookieParser() - mounts all auth routes on
/api/v1/auth
So final route pattern becomes:
/api/v1/auth/register/api/v1/auth/login- etc.
This project expects:
PORTMONGO_URIJWT_SECRET
src/config/config.js does:
dotenv.config()to read.env- throws error if
MONGO_URIorJWT_SECRETis missing
Important:
- keep
.envprivate - never push real secrets to GitHub
Fields:
username(unique, required)email(unique, required)password(required, stored as hash)timestamps(createdAt,updatedAt)
Each login/register creates a session record.
Fields:
user(reference toUserdocument)refreshTokenHash(hash of refresh token, not plain token)ip(request IP)userAgent(browser/app info)revoke(boolean, defaultfalse)timestamps
Why Session model matters:
- lets you invalidate refresh tokens (logout)
- supports "logout all devices"
- avoids storing plain refresh tokens in DB
- short life:
15m - sent in JSON response
- client sends it in
Authorization: Bearer <token> - used for protected APIs like
/get-me
- longer life:
7d - stored in
httpOnlycookie - used to get a new access token
- rotated in
/refresh-token(new one issued)
- password is converted to SHA-256 hash using Node
crypto - only hash is saved in DB
- during login, input password is hashed again and compared
All routes are defined in src/routes/users.route.js.
Controller: registerUser
Flow:
- Read
username,email,passwordfrom body. - Check if username/email already exists.
- Hash password with SHA-256.
- Create new user in MongoDB.
- Create refresh token (
7d). - Hash refresh token and store in
Sessioncollection withip+userAgent. - Create access token (
15m) with payload{ id, sessionId }. - Set refresh token in cookie:
httpOnly: truesecure: truesameSite: "strict"maxAge: 7 days
- Return user info + access token.
Controller: loginUser
Flow:
- Read
email,password. - Find user by email.
- Hash incoming password and compare with DB hash.
- If valid, generate refresh token (
7d). - Hash refresh token and create a new session.
- Generate access token (
15m) with payload{ id, sessionId }. - Set refresh token cookie.
- Return user info + access token.
Controller: getMe
Flow:
- Read bearer token from
Authorizationheader. - Verify JWT using
JWT_SECRET. - Read user by
decoded.id. - Return basic user info.
Controller: refreshToken
Flow:
- Read refresh token from cookie (
req.cookies.refreshToken). - If missing, return unauthorized.
- Verify refresh token JWT.
- Hash incoming refresh token.
- Find non-revoked matching session in DB.
- If session not found, return unauthorized.
- Create new access token (
15m). - Create new refresh token (
7d). - Hash new refresh token and update same session record.
- Set new refresh token cookie.
- Return new access token.
This route is the core of token rotation.
Controller: logoutUser
Flow:
- Read refresh token from cookie.
- Hash token and find active session.
- Mark session
revoke = true. - Clear
refreshTokencookie. - Return success.
Effect:
- current device/session is logged out
- that refresh token can no longer be used
Controller: logoutAllSessions
Flow:
- Read refresh token from cookie.
- Verify it and get user id.
- Revoke all active sessions for that user (
updateMany). - Clear cookie.
- Return success.
Effect:
- user is logged out from all devices/sessions
- User registers.
- Server creates
User. - Server creates
Session+ refresh token cookie. - Server returns access token.
- Client uses access token for protected requests.
- Access token expires after 15 minutes.
- Client calls
/refresh-tokenusing cookie. - Server validates session and rotates refresh token.
- Server sends new access token (+ updated cookie).
- User can logout current session or all sessions.
Current cookie options:
httpOnly: true
JS in browser cannot read cookie directly (helps against XSS token theft).secure: true
cookie only sent over HTTPS.sameSite: "strict"
blocks cross-site sending in many cases.maxAge: 7 days
cookie expires after 7 days.
Note for local development:
- with plain
http://localhost,secure: truemay prevent cookie being set in some environments.
- Install packages:
npm install- Create
.envwith:
PORT=3000
MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_super_secret_key- Start dev server:
npm run dev- Base URL:
http://localhost:3000/api/v1/authPOST /registerPOST /login
GET /get-me
GET /refresh-tokenGET /logoutGET /logout-all
- Call
POST /register. - Save returned
accessToken. - Confirm response also sets
refreshTokencookie. - Call
GET /get-mewith header:Authorization: Bearer <accessToken> - Call
GET /refresh-tokenwith cookie sent automatically by client. - Replace old access token with new one from response.
- Call
GET /logoutorGET /logout-all.
- This project combines stateless JWT auth with DB-backed sessions.
- Access token is short-lived for safety.
- Refresh token is cookie-based and revocable through session records.
- Hashing refresh token before storing is a good practice.
- Use
bcryptinstead of plain SHA-256 for password hashing. - Add validation for request body inputs.
- Add centralized error handling middleware.
- Add auth middleware instead of repeating token checks in controllers.
- Add CORS config if frontend is on a different domain.
- Add tests for login/refresh/logout flows.
This codebase already teaches the most important auth building blocks:
- user creation
- secure password storage (hashed)
- login verification
- access + refresh token model
- refresh token rotation
- session revocation for logout and logout-all
If you understand this README and map each section to its file, you can explain this backend end-to-end to another beginner.