Creating Login Endpoint & Login Existing User in CloudNoteBook 🔐👤
So far in CloudNoteBook, we've built a Sign Up endpoint that creates a new user and
stores their hashed password in MongoDB. But a real app also needs a way for
existing users to log back in. That means checking their email, comparing their
password securely, and issuing them a fresh JWT (JSON Web Token) to prove they're
authenticated on future requests.
In this tutorial, we will learn:
What a login endpoint does and why JWTs matter
Adding validation rules for email and password using express-validator
Finding an existing user by email with Mongoose
Safely comparing passwords using bcrypt.compare
Generating and returning a JWT auth token on successful login
Handling invalid credentials with proper error responses
---
What Does a Login Endpoint Do?
A login endpoint takes a user's email and password, checks them against what's stored
in the database, and — if they match — returns a JWT auth token. This token is sent back
by the frontend on every future request (like fetching or adding notes) so our server knows
who is making the request, without asking the user to log in again and again.
---
Step 1: Import What We Need in routes/auth.js
Open routes/auth.js — the same file where we created the Sign Up route — and make
sure these packages are imported at the top.
const express = require("express");
const router = express.Router();
const User = require("../models/User");
const { body, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const JWT_SECRET = "CloudNoteBook$ecretKey";
---
Step 2: Define the Login Route with Validation
We create a new route: POST /api/auth/login. We validate that the email is a
valid email address and the password is not empty, using express-validator.
// ROUTE 2: Login an existing user using: POST "/api/auth/login" — No login required
router.post(
"/login",
[
body("email", "Enter a valid email").isEmail(),
body("password", "Password cannot be blank").exists(),
],
async (req, res) => {
// We will fill this in the next step
}
);
---
Step 3: Check Validation Errors and Find the User
Inside the route handler, first check if there are any validation errors. Then look up the
user in MongoDB by their email. If no such user exists, we return an error —
without revealing whether it was the email or password that was wrong (this is good security practice).
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
try {
let user = await User.findOne({ email });
if (!user) {
return res
.status(400)
.json({ error: "Please try to login with correct credentials" });
}
} catch (error) {
console.error(error.message);
res.status(500).send("Internal Server Error");
}
---
Step 4: Compare the Password with bcrypt
Since we never store plain-text passwords, we can't check password === user.password
directly. Instead, we use bcrypt.compare, which hashes the entered password the same
way and checks it against the stored hash.
const passwordCompare = await bcrypt.compare(password, user.password);
if (!passwordCompare) {
return res
.status(400)
.json({ error: "Please try to login with correct credentials" });
}
---
Step 5: Generate and Return the JWT Auth Token
Once the password matches, we sign a JWT containing the user's MongoDB id and send
it back as authtoken. The frontend will store this token and attach it to future
requests to prove the user is logged in.
const data = {
user: {
id: user.id,
},
};
const authtoken = jwt.sign(data, JWT_SECRET);
res.json({ success: true, authtoken });
---
Step 6: Full Updated Login Route
Here is the complete /login route combined into one block, ready to sit alongside
your existing /createuser route in routes/auth.js.
const express = require("express");
const router = express.Router();
const User = require("../models/User");
const { body, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const JWT_SECRET = "CloudNoteBook$ecretKey";
// ROUTE 2: Login an existing user using: POST "/api/auth/login" — No login required
router.post(
"/login",
[
body("email", "Enter a valid email").isEmail(),
body("password", "Password cannot be blank").exists(),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
try {
let user = await User.findOne({ email });
if (!user) {
return res
.status(400)
.json({ error: "Please try to login with correct credentials" });
}
const passwordCompare = await bcrypt.compare(password, user.password);
if (!passwordCompare) {
return res
.status(400)
.json({ error: "Please try to login with correct credentials" });
}
const data = {
user: {
id: user.id,
},
};
const authtoken = jwt.sign(data, JWT_SECRET);
res.json({ success: true, authtoken });
} catch (error) {
console.error(error.message);
res.status(500).send("Internal Server Error");
}
}
);
module.exports = router;
---
Testing the Endpoint in Postman
Send a POST request to http://localhost:5000/api/auth/login with a JSON
body containing email and password. On success, you'll get back an
authtoken — save this, we'll use it in the next tutorial to access protected routes
like fetching a user's notes.
| Scenario |
Response |
Status Code |
| Valid email & correct password |
{ success: true, authtoken } |
200 |
| Email not registered |
{ error: "Please try to login with correct credentials" } |
400 |
| Wrong password for existing email |
{ error: "Please try to login with correct credentials" } |
400 |
| Missing/invalid email or password field |
{ errors: [...] } from express-validator |
400 |
---
Features and Learnings:-
Understood the purpose of a login endpoint and how JWTs enable stateless authentication.
Validated email and password input using express-validator.
Looked up an existing user in MongoDB with User.findOne({ email }).
Safely compared plain-text and hashed passwords using bcrypt.compare.
Returned a generic error message to avoid leaking whether the email or password was wrong.
Generated and returned a JWT authtoken on successful login using jsonwebtoken.
Tested the /api/auth/login endpoint in Postman and reviewed all response scenarios.
Prepared the app for the next step: creating protected routes using JWT-based middleware.