Skip to main content

Authentication System

Overview

The Notes App implements a JWT (JSON Web Token) based authentication system that secures user accounts and protects API endpoints. This system ensures that only authenticated users can access their personal notes and account information.

What is JWT Authentication?

JWT (JSON Web Token) is a compact, URL-safe token format used for securely transmitting information between parties. In this application:

  • Stateless: The server doesn't need to store session information
  • Self-contained: The token contains all necessary user information
  • Secure: Tokens are signed with a secret key to prevent tampering

Authentication Components

Environment Configuration

File: backend/.env

ACCESS_TOKEN_SECRET=9IRZS+s861Z9W6YwDLFGFBrkG0WcWyqWnGC8WiuKp/E=

The ACCESS_TOKEN_SECRET is a cryptographic key used to:

  • Sign tokens when they are created
  • Verify tokens when they are received
  • Ensure token integrity by detecting tampering

Authentication Middleware

File: backend/utilities.js

The authenticateToken function is middleware - code that runs between receiving a request and sending a response.

function authenticateToken(req, res, next) {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];

if(!token) return res.sendStatus(401);
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if(err) return res.sendStatus(401);
req.user = user;
next();
});
}

How the Middleware Works

  1. Extract Authorization Header: Gets the Authorization header from the HTTP request
  2. Parse Token: Splits the header value and extracts the token (format: "Bearer <token>")
  3. Check Token Presence: Returns 401 (Unauthorized) if no token is provided
  4. Verify Token: Uses JWT library to verify the token with the secret key
  5. Handle Verification:
    • If verification fails: Returns 401 (Unauthorized)
    • If successful: Adds user data to req.user and calls next() to continue

Key Concepts Explained

  • req.headers["authorization"]: Accesses the Authorization HTTP header
  • authHeader.split(" ")[1]: Splits "Bearer token123" and takes the second part (token123)
  • res.sendStatus(401): Sends HTTP 401 Unauthorized status
  • req.user = user: Attaches decoded user information to the request object
  • next(): Passes control to the next middleware or route handler

Authentication Flow

User Registration

Endpoint: POST /create-account

app.post("/create-account", async (req, res) => {
const { fullName, email, password } = req.body;

// Validation checks...

const user = new User({
fullName,
email,
password,
});

await user.save();

const accessToken = jwt.sign({ user }, process.env.ACCESS_TOKEN_SECRET, {
expiresIn: "36000m",
});

return res.json({
error: false,
user,
accessToken,
message: "Registration Successful",
});
});

Registration Process

  1. Extract user data from request body
  2. Validate required fields (fullName, email, password)
  3. Check for existing user with the same email
  4. Create new user in database
  5. Generate JWT token containing user information
  6. Return token and user data to client

Token Generation Details

  • jwt.sign({ user }, secret, options): Creates a signed token
  • Payload: { user } - Contains user information
  • Secret: Environment variable for signing
  • Expiration: "36000m" - Token expires in 36,000 minutes (25 days)

User Login

Endpoint: POST /login

app.post("/login", async (req, res) => {
const { email, password } = req.body;

// Validation...

const userInfo = await User.findOne({ email: email });

if (userInfo.email == email && userInfo.password == password) {
const user = { user: userInfo };
const accessToken = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, {
expiresIn: "36000m",
});

return res.json({
error: false,
message: "Login Successful",
email,
accessToken,
});
}
});

Login Process

  1. Extract credentials from request body
  2. Find user in database by email
  3. Compare passwords (plain text comparison - security issue)
  4. Generate new JWT token if credentials match
  5. Return token for future authenticated requests

Protected Endpoints

All note-related endpoints use the authenticateToken middleware:

app.get("/get-user", authenticateToken, async (req, res) => {
const { user } = req.user; // User data from verified token
// ... endpoint logic
});

app.post("/add-note", authenticateToken, async (req, res) => {
const { user } = req.user; // Access authenticated user
// ... create note for this user
});

How Protection Works

  1. Client sends request with Authorization: Bearer &lt;token> header
  2. Middleware runs first before the endpoint handler
  3. Token is verified and user data extracted
  4. Request continues to endpoint with req.user populated
  5. Endpoint uses user data to ensure data isolation

Client-Side Token Usage

The frontend must include the token in API requests:

// Example request format
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}

Security Considerations

Current Implementation Issues

  1. Plain text passwords: Passwords stored without hashing
  2. Long token expiration: 25-day expiration may be excessive
  3. No password complexity requirements: Weak passwords allowed
  4. No rate limiting: No protection against brute force attacks

Security Features Present

  1. Token-based authentication: Stateless and scalable
  2. Data isolation: Users can only access their own data
  3. Secret key protection: Token signing key stored in environment variables
  4. Authorization header: Standard HTTP authentication method

Connection to Other Components

  • Database Models: Authentication creates and validates User records
  • API Endpoints: All note operations require authentication
  • Frontend: Must store and send tokens with requests
  • Middleware: Runs before protected endpoints to verify identity