Skip to content

cu-fs1/6b

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 

Repository files navigation

6b

A beginner-friendly Express + MongoDB authentication API with:

  • user registration and login
  • refresh token rotation (/users/refresh)
  • logout with refresh token invalidation (/users/logout)
  • JWT-based protected route (/users/me)
  • password hashing with Argon2
  • request logging (optional SolarWinds forwarding)
  • centralized error handling

This README is written so students can implement almost the same project from scratch.

1. Tech Stack

  • Node.js (ES modules)
  • Express 5
  • MongoDB + Mongoose
  • Argon2 (password hashing)
  • JSON Web Token (jsonwebtoken)
  • http-errors + http-status-codes
  • Axios (for optional SolarWinds log shipping)
  • pnpm + nodemon

2. Folder Structure

config/
  db.js
  solarwinds.js
controllers/
  auth.controller.js
middleware/
  auth.middleware.js
  error.middleware.js
  logger.middleware.js
models/
  user.model.js
routes/
  auth.routes.js
utils/
  jwt.js
index.js

3. Installation and Run

  1. Install dependencies.
pnpm install
  1. Create .env in the project root.
PORT=3000
MONGO_URI=your_mongo_connection_string
JWT_SECRET=your_super_secret_key
JWT_EXPIRES_IN=15m
JWT_REFRESH_SECRET=your_refresh_secret_key
JWT_REFRESH_EXPIRES_IN=7d
SOLARWINDS_TOKEN=optional
  1. Start development server.
pnpm dev
  1. For production mode.
pnpm start

4. How the App Boots (index.js)

Startup flow:

  1. Load environment variables (dotenv.config()).
  2. Connect to MongoDB (connectDB()).
  3. Register middlewares (cors(), express.json(), loggerMiddleware).
  4. Mount auth routes at /users.
  5. Add health/base route /.
  6. Register errorMiddleware as the last middleware.
  7. Listen on PORT (default 3000).

Why error middleware is last:

  • Express forwards thrown errors to the next error handler.
  • If placed earlier, it will not catch errors from later route handlers.

5. Database Layer

config/db.js

  • Reads MONGO_URI from .env.
  • Throws a clear error if missing.
  • Connects with mongoose.connect(uri).

models/user.model.js

User schema fields:

  • fullName: required, trimmed string
  • email: required, unique, lowercased, trimmed, regex validated
  • password: required, minimum length 6
  • refreshToken: stored refresh token string (or null on logout)

Custom validation messages included:

  • "Full name is required"
  • "Email is required"
  • "Please provide a valid email address"
  • "Password is required"
  • "Password must be at least 6 characters long"

Security behavior:

  • pre("save") hook hashes password using Argon2.
  • Hashing runs only when password is modified.
  • comparePassword(candidatePassword) verifies login password.

6. JWT Utilities

utils/jwt.js

  • getJwtSecret() ensures JWT_SECRET exists.
  • getRefreshSecret() ensures JWT_REFRESH_SECRET exists.
  • generateToken(userId) signs payload { userId }.
  • generateToken(userId) uses JWT_EXPIRES_IN (default 15m).
  • generateRefreshToken(userId) uses JWT_REFRESH_EXPIRES_IN (default 7d).
  • verifyAccessToken(token) verifies access tokens.
  • verifyRefreshToken(token) verifies refresh tokens.

7. Middlewares

middleware/auth.middleware.js

  • Expects header format: Authorization: Bearer <token>
  • Verifies token with verifyAccessToken(...)
  • Stores decoded payload in req.user
  • Returns 401 Unauthorized with "Unauthorized" when header/token missing.
  • Returns 401 Unauthorized with "Invalid or expired token" when verification fails.

middleware/logger.middleware.js

  • Tracks request start and finish time.
  • Creates a structured log object after response is sent.
  • Calls sendToSolarWinds(logEntry).

middleware/error.middleware.js

  • Central error formatter.
  • Uses err.statusCode or err.status when available.
  • Falls back to 500 Internal Server Error.
  • Returns JSON:
{
  "success": false,
  "message": "..."
}
  • Includes stack trace only when NODE_ENV=development.

8. Optional Log Shipping

config/solarwinds.js

  • If SOLARWINDS_TOKEN is not set, it silently skips sending logs.
  • If token exists, it POSTs logs to SolarWinds collector API.
  • Errors are printed in console but do not break API responses.

This keeps observability optional for classroom/local setups.

9. Routes and Controllers

routes/auth.routes.js

  • POST /users/register -> registerUser
  • POST /users/login -> loginUser
  • POST /users/refresh -> refreshAccessToken
  • POST /users/logout -> authMiddleware -> logoutUser
  • GET /users/me -> authMiddleware -> getCurrentUser

controllers/auth.controller.js

registerUser

  • Reads fullName, email, password from request body.
  • Creates user with User.create(...).
  • Returns 201 Created and safe user fields (no password).

loginUser

  • Finds user by email.
  • Validates password via comparePassword.
  • Throws 401 ("Invalid email or password") on failure.
  • Returns access_token, refresh_token, token_type, and user basics on success.

refreshAccessToken

  • Requires refresh_token in request body.
  • Verifies JWT signature and expiry with verifyRefreshToken.
  • Confirms the refresh token matches the one stored for that user.
  • Rotates tokens on success (issues new access + refresh token pair).
  • Returns 401 for invalid/expired/mismatched refresh tokens.

logoutUser

  • Requires a valid access token.
  • Clears stored refreshToken for the authenticated user.
  • Returns 200 with a logout confirmation message.

getCurrentUser

  • Uses req.user.userId from auth middleware.
  • Fetches user and excludes password via .select("-password").
  • Throws 404 if user no longer exists.

10. API Contract

Base URL

http://localhost:3000

POST /users/register

Request:

{
  "fullName": "Jane Doe",
  "email": "jane@example.com",
  "password": "strongPassword123"
}

Success (201):

{
  "message": "User registered successfully",
  "data": {
    "id": "...",
    "fullName": "Jane Doe",
    "email": "jane@example.com",
    "createdAt": "..."
  }
}

POST /users/login

Request:

{
  "email": "jane@example.com",
  "password": "strongPassword123"
}

Success (200):

{
  "message": "Login successful",
  "data": {
    "access_token": "<jwt>",
    "refresh_token": "<refresh-jwt>",
    "token_type": "Bearer",
    "id": "...",
    "fullName": "Jane Doe",
    "email": "jane@example.com"
  }
}

POST /users/refresh

Request:

{
  "refresh_token": "<refresh-jwt>"
}

Success (200):

{
  "message": "Token refreshed successfully",
  "data": {
    "access_token": "<new-access-jwt>",
    "refresh_token": "<new-refresh-jwt>",
    "token_type": "Bearer"
  }
}

POST /users/logout

Header:

Authorization: Bearer <jwt>

Success (200):

{
  "message": "Logged out successfully"
}

GET /users/me

Header:

Authorization: Bearer <jwt>

Success (200):

{
  "message": "User fetched successfully",
  "data": {
    "_id": "...",
    "fullName": "Jane Doe",
    "email": "jane@example.com",
    "createdAt": "...",
    "updatedAt": "..."
  }
}

11. Common Errors Students Should Expect

  • Validation failure while registering:
  • by default this codebase returns an error via errorMiddleware (typically 500 unless status is set), and the message includes Mongoose validation text.
  • Wrong login credentials:
  • 401 with "Invalid email or password".
  • Missing or invalid token:
  • 401 with "Unauthorized" or "Invalid or expired token".
  • Missing/invalid/expired refresh token:
  • 400 ("Refresh token is required") or 401 ("Invalid or expired refresh token").
  • Missing JWT_SECRET or MONGO_URI:
  • startup/runtime error with clear message in console.
  • Missing JWT_REFRESH_SECRET:
  • startup/runtime error with clear message in console.

12. Quick Manual Testing (cURL)

Register:

curl -X POST http://localhost:3000/users/register \
  -H "Content-Type: application/json" \
  -d '{"fullName":"Jane Doe","email":"jane@example.com","password":"strongPassword123"}'

Login:

curl -X POST http://localhost:3000/users/login \
  -H "Content-Type: application/json" \
  -d '{"email":"jane@example.com","password":"strongPassword123"}'

Refresh access token (replace <REFRESH_TOKEN>):

curl -X POST http://localhost:3000/users/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token":"<REFRESH_TOKEN>"}'

Get current user (replace <TOKEN>):

curl http://localhost:3000/users/me \
  -H "Authorization: Bearer <TOKEN>"

Logout (replace <TOKEN>):

curl -X POST http://localhost:3000/users/logout \
  -H "Authorization: Bearer <TOKEN>"

13. Build This Yourself (Suggested Order)

  1. Setup Express app and folder structure.
  2. Add Mongo connection helper (config/db.js).
  3. Create user model with validation + Argon2 hooks.
  4. Add JWT utility functions.
  5. Implement register, login, refresh, and logout controllers.
  6. Add auth middleware and protected routes (/users/me, /users/logout).
  7. Add token rotation logic for /users/refresh.
  8. Add error middleware and use http-errors for consistency.
  9. Add logger middleware and optional SolarWinds forwarding.
  10. Test all endpoints with Postman or cURL.

Notes

  • Use pnpm for consistency with this project.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors