Skip to main content

Overview

The Resume Generator uses a secure JWT-based authentication system with HTTP-only cookies for session management. The system includes user registration, login/logout flows, token blacklisting for secure logout, and middleware-based route protection.

Authentication Flow

User Registration

New users register with a username, email, and password. Passwords are hashed using bcrypt before storage. API Endpoint: POST /api/auth/register Request Body:
{
  "username": "johndoe",
  "email": "[email protected]",
  "password": "securePassword123"
}
Backend Flow: The registration controller validates input, checks for existing users, hashes the password, and creates a new user account:
auth.controller.js:11-57
async function registerUserController(req, res) {
    const { username, email, password } = req.body

    if (!username || !email || !password) {
        return res.status(400).json({
            message: "Please provide username, email and password"
        })
    }

    const isUserAlreadyExists = await userModel.findOne({
        $or: [ { username }, { email } ]
    })

    if (isUserAlreadyExists) {
        return res.status(400).json({
            message: "Account already exists with this email address or username"
        })
    }

    const hash = await bcrypt.hash(password, 10)

    const user = await userModel.create({
        username,
        email,
        password: hash
    })

    const token = jwt.sign(
        { id: user._id, username: user.username },
        process.env.JWT_SECRET,
        { expiresIn: "1d" }
    )

    res.cookie("token", token)

    res.status(201).json({
        message: "User registered successfully",
        user: {
            id: user._id,
            username: user.username,
            email: user.email
        }
    })
}
After successful registration, a JWT token is automatically generated and set as an HTTP-only cookie, logging the user in immediately.

User Login

Existing users authenticate with their email and password. API Endpoint: POST /api/auth/login Request Body:
{
  "email": "[email protected]",
  "password": "securePassword123"
}
Backend Flow: The login controller validates credentials and issues a JWT token:
auth.controller.js:65-100
async function loginUserController(req, res) {
    const { email, password } = req.body

    const user = await userModel.findOne({ email })

    if (!user) {
        return res.status(400).json({
            message: "Invalid email or password"
        })
    }

    const isPasswordValid = await bcrypt.compare(password, user.password)

    if (!isPasswordValid) {
        return res.status(400).json({
            message: "Invalid email or password"
        })
    }

    const token = jwt.sign(
        { id: user._id, username: user.username },
        process.env.JWT_SECRET,
        { expiresIn: "1d" }
    )

    res.cookie("token", token)
    res.status(200).json({
        message: "User loggedIn successfully.",
        user: {
            id: user._id,
            username: user.username,
            email: user.email
        }
    })
}

User Logout

Secure logout with token blacklisting prevents reuse of expired tokens. API Endpoint: GET /api/auth/logout Backend Flow: The logout controller adds the current token to a blacklist and clears the cookie:
auth.controller.js:108-120
async function logoutUserController(req, res) {
    const token = req.cookies.token

    if (token) {
        await tokenBlacklistModel.create({ token })
    }

    res.clearCookie("token")

    res.status(200).json({
        message: "User logged out successfully"
    })
}
Blacklisted tokens are stored in the database to prevent token reuse even before expiration. The auth middleware checks against this blacklist on every protected request.

User Interface Flows

Registration Page

The registration form collects username, email, and password from new users:
  1. User navigates to /register
  2. Fills in username, email, and password fields
  3. Clicks “Register” button
  4. On success, automatically redirected to home page (/) as authenticated user
  5. Loading state displayed during registration

Login Page

Existing users authenticate through the login form:
  1. User navigates to /login
  2. Enters email and password
  3. Clicks “Login” button
  4. On success, redirected to home page (/)
  5. Link to registration page for new users

Authentication Middleware

Protected routes use the authUser middleware to verify JWT tokens and check the blacklist:
auth.middleware.js:6-40
async function authUser(req, res, next) {
    const token = req.cookies.token

    if (!token) {
        return res.status(401).json({
            message: "Token not provided."
        })
    }

    const isTokenBlacklisted = await tokenBlacklistModel.findOne({
        token
    })

    if (isTokenBlacklisted) {
        return res.status(401).json({
            message: "token is invalid"
        })
    }

    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET)

        req.user = decoded

        next()

    } catch (err) {
        return res.status(401).json({
            message: "Invalid token."
        })
    }
}
Middleware validates:
  • Token exists in cookies
  • Token is not blacklisted
  • Token signature is valid
  • Token is not expired

Data Models

User Schema

user.model.js:4-21
const userSchema = new mongoose.Schema({
    username: {
        type: String,
        unique: [ true, "username already taken" ],
        required: true,
    },

    email: {
        type: String,
        unique: [ true, "Account already exists with this email address" ],
        required: true,
    },

    password: {
        type: String,
        required: true
    }
})

Token Blacklist Schema

blacklist.model.js:4-11
const blacklistTokenSchema = new mongoose.Schema({
    token: {
        type: String,
        required: [ true, "token is required to be added in blacklist" ]
    }
}, {
    timestamps: true
})

Protected Routes

Routes that require authentication use the authUser middleware:
auth.routes.js:36
authRouter.get("/get-me", authMiddleware.authUser, authController.getMeController)
Examples of protected endpoints:
  • GET /api/auth/get-me - Get current user details
  • POST /api/interview/ - Generate interview report
  • GET /api/interview/ - Get all user’s interview reports
  • POST /api/interview/resume/pdf/:interviewReportId - Generate resume PDF

Security Features

Password Hashing

Passwords are hashed with bcrypt (10 rounds) before database storage

HTTP-Only Cookies

JWT tokens stored in HTTP-only cookies prevent XSS attacks

Token Blacklisting

Logged-out tokens are blacklisted to prevent reuse

Token Expiration

JWT tokens expire after 24 hours (1 day)

Get Current User

Authenticated users can retrieve their profile information: API Endpoint: GET /api/auth/get-me Requires: Valid JWT token in cookies Response:
{
  "message": "User details fetched successfully",
  "user": {
    "id": "507f1f77bcf86cd799439011",
    "username": "johndoe",
    "email": "[email protected]"
  }
}
The password hash is never returned in API responses for security.

Build docs developers (and LLMs) love