I have a NextJS frontend and Node/ExpressJS backend. On signup or login, I am generating a jwt cookie and setting it as application header on my frontend. Once logged in, the cookie is attached to any further requests and the backend verifies the cookie. Everything is working fine in localhost. However, on deploying my server on DigitalOcean as an App, the frontend seems to not be sending the cookie. I am able to login and register, but not able to do anything where cookie has to be sent. I have tries turning secure true and false, httpOnly, sameSite, etc all the options, but nothing seems to work.
Here is my authController:
import bcrypt from "bcrypt";
import { Request, Response } from "express";
import jwt from "jsonwebtoken";
import User from "../models/userModel.js";
import {
generateOTP,
resendOTP,
sendOTP,
validateOTP,
} from "../utils/nodemailer.js";
const JWT_SECRET = process.env.JWT_SECRET!;
export const registerController = async (req: Request, res: Response) => {
const { firstName, lastName, email, password } = req.body;
if (!email.endsWith("@bitmesra.ac.in")) {
return res.status(400).json({ message: "Invalid email domain" });
}
try {
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ message: "User already exists" });
}
const hashedPassword = await bcrypt.hash(password, 10);
const otp = generateOTP();
const newUser = new User({
firstName,
lastName,
email,
hashedPassword,
otp,
isVerified: false,
otpRequestDate: new Date(),
otpRequestCount: 1,
});
await newUser.save();
await sendOTP(email, otp);
res.status(201).json({
message: "User registered. OTP sent to email.",
user: { _id: newUser._id },
});
} catch (error) {
res.status(500).json({ message: "Error registering user", error });
}
};
export const verifyOtpController = async (req: Request, res: Response) => {
const userId = req.params.id;
const { otp } = req.body;
console.log("User ID:", userId);
console.log("OTP:", otp);
try {
const isValid = await validateOTP(userId, otp);
if (!isValid) {
return res.status(400).json({ message: "Invalid OTP" });
}
const user = await User.findById(userId);
if (user) {
user.isVerified = true;
user.otp = undefined;
await user.save();
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET!, {
expiresIn: "7d",
});
res
.cookie("token", token, {
httpOnly: true,
secure: true,
sameSite: "none",
domain: "king-prawn-app-amyex.ondigitalocean.app",
})
.status(200)
.json({
message: "Account verified successfully",
user: {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
_id: user._id,
token: token,
},
});
} else {
res.status(404).json({ message: "User not found" });
}
} catch (error) {
res.status(500).json({ message: "Error verifying OTP" });
}
};
export const loginController = async (req: Request, res: Response) => {
const { email, password } = req.body;
try {
if (!email || !password) {
return res.status(400).json({ message: "Please fill in all fields" });
}
if (!email.endsWith("@bitmesra.ac.in")) {
return res.status(400).json({ message: "Invalid email domain" });
}
const user = await User.findOne({ email }).select("+hashedPassword");
if (!user) {
return res.status(404).json({ message: "Invalid Email or Password" });
}
const isMatch = await bcrypt.compare(password, user.hashedPassword!);
if (!isMatch) {
return res.status(400).json({ message: "Invalid Email or Password" });
}
if (!user.isVerified) {
const otp = generateOTP();
user.otp = otp;
user.otpLastRequestedAt = new Date();
await user.save();
await sendOTP(email, otp);
return res.status(403).json({
message:
"Your account is not verified. A verification email has been sent to your email address.",
userId: user._id,
});
}
const token = jwt.sign({ id: user._id }, JWT_SECRET, { expiresIn: "7d" });
res
.cookie("token", token, {
httpOnly: true,
secure: true,
sameSite: "none",
domain: "king-prawn-app-amyex.ondigitalocean.app",
})
.status(200)
.json({
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
_id: user._id,
token,
message: "Logged in successfully",
});
} catch (error) {
res.status(500).json({ message: "Error logging in", error });
}
};
Item Controller:
export const createItem = async (req: Request, res: Response) => {
try {
const userId = req.user?.id;
const {
title,
description,
price,
room_no,
hostel_no,
year_of_purchase,
category,
contact_no,
} = req.body;
const existingItem = await Item.findOne({ title, seller: userId });
if (existingItem) {
return res
.status(400)
.json({ message: "You already have an item with this title" });
}
// Calculate total size
const totalSize = (req.files as Express.Multer.File[]).reduce(
(acc: number, file: Express.Multer.File) => acc + file.size,
0
);
if (totalSize > MAX_FILE_SIZE) {
return res
.status(400)
.json({ message: "Total file size exceeds the 25 MB limit." });
}
// Resize and upload each image
const imageDetails = [];
for (const file of req.files as Express.Multer.File[]) {
const buffer = await sharp(file.buffer).toFormat("webp").toBuffer();
const imageName = randomImageName();
const params = {
Bucket: bucketName!,
Key: imageName,
Body: buffer,
ContentType: file.mimetype,
};
const command = new PutObjectCommand(params);
await s3.send(command);
imageDetails.push({ key: imageName });
}
const newItem = new Item({
title,
description,
price,
room_no,
hostel_no,
year_of_purchase,
category,
seller: userId,
images: imageDetails,
contact_no,
});
await newItem.save();
await User.findByIdAndUpdate(userId, { $push: { items: newItem._id } });
res.status(201).json({ message: "Item created Successfully", newItem });
} catch (error) {
res.status(500).json({ message: "Server error", error });
}
};
Routes:
import { Router } from "express";
import { uploadMiddleware } from "../config/multerConfig.js";
import {
createItem,
deleteImage,
deleteItem,
getAllItems,
getItemById,
getItems,
updateImages,
updateItem,
} from "../controllers/itemController.js";
import { authMiddleware } from "../middlewares/authMiddleware.js";
const router = Router();
router.get("/all", getAllItems);
router.use(authMiddleware);
router.route("/").post(uploadMiddleware, createItem).get(getItems);
router.route("/:id").get(getItemById).put(updateItem).delete(deleteItem);
router.patch("/:id/images", uploadMiddleware, updateImages);
router.delete("/:itemId/images/:imageId", deleteImage);
export default router;
Auth Middleware:
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
const JWT_SECRET = process.env.JWT_SECRET!;
export const authMiddleware = (
req: Request,
res: Response,
next: NextFunction
) => {
const token = req.cookies.token;
// console.log("token:", token);
if (!token) {
return res.status(401).json({ message: "No token provided" });
}
try {
const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
//console.log("decoded:", decoded);
req.user = { id: decoded.id };
next();
} catch (error) {
return res.status(401).json({ message: "Invalid token" });
}
};
The full backend code is in this repo: text
The client code is in this repo: text