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
- Extract Authorization Header: Gets the
Authorization
header from the HTTP request - Parse Token: Splits the header value and extracts the token (format: "Bearer <token>")
- Check Token Presence: Returns 401 (Unauthorized) if no token is provided
- Verify Token: Uses JWT library to verify the token with the secret key
- Handle Verification:
- If verification fails: Returns 401 (Unauthorized)
- If successful: Adds user data to
req.user
and callsnext()
to continue
Key Concepts Explained
req.headers["authorization"]
: Accesses the Authorization HTTP headerauthHeader.split(" ")[1]
: Splits "Bearer token123" and takes the second part (token123)res.sendStatus(401)
: Sends HTTP 401 Unauthorized statusreq.user = user
: Attaches decoded user information to the request objectnext()
: 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
- Extract user data from request body
- Validate required fields (fullName, email, password)
- Check for existing user with the same email
- Create new user in database
- Generate JWT token containing user information
- 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
- Extract credentials from request body
- Find user in database by email
- Compare passwords (plain text comparison - security issue)
- Generate new JWT token if credentials match
- 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
- Client sends request with
Authorization: Bearer <token>
header - Middleware runs first before the endpoint handler
- Token is verified and user data extracted
- Request continues to endpoint with
req.user
populated - 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
- Plain text passwords: Passwords stored without hashing
- Long token expiration: 25-day expiration may be excessive
- No password complexity requirements: Weak passwords allowed
- No rate limiting: No protection against brute force attacks
Security Features Present
- Token-based authentication: Stateless and scalable
- Data isolation: Users can only access their own data
- Secret key protection: Token signing key stored in environment variables
- 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