Why is Chrome not setting cookie in production - node.js

I have an app in production, that sometimes sets a cookie in Chrome, and sometimes doesnt. Im using nodejs on backend. This doesnt happen in firefox, only Chrome. Am I doing something wrong?
Recently I added
this.app.use(cors({ origin: true, credentials: true }));
but it didnt help at all.
Code for setting cookie:
exports.oauthLogin = async (req, res) => {
if (!req.query.token) {
return res.status(400).send({ error: "No token provided" });
}
const settings = await AppSettings.findOne();
const response = await checkToken({
token: req.query.token,
serverUrl: settings.oauthServerUrl,
clientId: settings.oauthClientId,
clientSecret: settings.oauthClientSecret,
});
if (response.error) {
return res.redirect("/");
}
const { _id, displayName, email } = response.user;
let user = await User.findOne({ email: email });
if (!user) {
user = new User({
displayName,
email,
oauthId: _id,
accessLevel: "User",
});
await user.save();
}
const jwtUserData = {
userId: user._id,
userAccessLevel: user.accessLevel,
};
const token = jwt.sign(jwtUserData, process.env.JWT_SECRET);
res.cookie("token", token);
return res.redirect(req.query.redirectUrl ? req.query.redirectUrl : "/");
};

Related

Token Authorization failed with passport-jwt

I am testing on postman with passport-jwt, I got my user's token, and try to test protected routes with passport-jwt, following my code, I am supposed to get "hello", but somehow still not allow me to authorize, and it keep gives me "401 unauthorized", can anyone help if my code is wrong? thanks!
on my postman, i have attached authorization, and Bearer token under header.
My middleware:
const jwt = require("jsonwebtoken");
const User = require("../models/user");
const { signupUser, loginUser } = require("../controller/auth");
const passport = require("passport");
const JwtStrategy = require("passport-jwt").Strategy,
ExtractJwt = require("passport-jwt").ExtractJwt;
const opts = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: "process.env.JWT_SECRET",
};
module.exports = (passport) => {
passport.use(
new JwtStrategy(opts, async (payload, done) => {
await User.findById(payload.id)
.then((user) => {
if (user) {
return done(null, user);
}
return done(null, false);
})
.catch((err) => {
console.log(err);
return done(null, false);
});
})
);
};
the route file:
const express = require("express");
const router = express.Router();
const { userAuth, signupUser, loginUser } = require("../controller/auth");
router.get("/category/getcategory", userAuth, async (req, res) => {
return res.json("hello");
});
module.exports = router;
My controller file:
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const passport = require("passport");
exports.loginUser = async (req, role, res) => {
const { email, password } = req;
try {
const user = await User.findOne({ email });
if (!user)
return res.status(400).json({ message: "User does not exists." });
const isPasswordCorrect = await bcrypt.compare(password, user.password);
if (!isPasswordCorrect)
return res.status(400).json({ message: "Invalid credentials." });
if (!user.role == role) {
return res.status(403).json({ message: "please check the right portal" });
}
const payload = {
email: user.email,
id: user._id,
role: user.role,
};
const token = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: "1h",
});
let result = {
mail: user.email,
id: user._id,
role: user.role,
token: `Bearer ${token}`,
expiresIn: 168,
};
res.status(200).send({ ...result, Message: "Now you are logged in!" });
} catch (error) {
console.log(error);
}
};
exports.signupUser = async (req, role, res) => {
const { firstName, lastName, email, password, confirmPassword } = req;
try {
const user = await User.findOne({ email });
if (user) return res.status(400).json({ message: "User already exists." });
if (!password == confirmPassword)
return res.status(400).json({ message: "Password don't match" });
const hashedPassword = await bcrypt.hash(password, 12);
const result = await User.create({
email,
password: hashedPassword,
firstName,
lastName,
role,
});
const token = jwt.sign(
{ email: result.email, id: result._id },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.status(200).json({ result, token });
} catch (error) {
console.log(error);
}
};
exports.userAuth = passport.authenticate("jwt", { session: false });

Cannot set headers to pass token in node js

node js
This is my register method to register a user. I am trying to pass token in headers when a user is registered which will be used in the front end to access the token and store it in the local storage.
module.exports.register = async function (req, res, next) {
try {
const { username, email, password } = req.body;
const profileImage = req.file.path;
const usernameCheck = await User.findOne({ username });
if (usernameCheck)
return res.json({ msg: "Username already used", status: false });
const emailCheck = await User.findOne({ email });
if (emailCheck)
return res.json({ msg: "Email already exists", status: false });
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({
_id: new mongoose.Types.ObjectId(),
username,
email,
profileImage,
password: hashedPassword,
});
delete user.password;
//create jwt token
const token = jwt.sign(
{
username: user.username,
email: user.email,
userId: user._id,
},
process.env.JWT_KEY,
{
expiresIn: "1h",
}
);
res.header("x-auth-token", token); //This is not setting the token in headers
return res.json({
message: "User Created Successfully",
status: true,
user,
});
} catch (ex) {
next(ex);
}
};
react js
This is my front-end react code to register a user. I want to login the user with the jwt token stored in localStorage once the user is registered.
const handleSubmit = async (values) => {
try {
const { username, email, profileImage, password } = values;
const formData = new FormData();
formData.append("username", username);
formData.append("email", email);
formData.append("profileImage", profileImage);
formData.append("password", password);
const response = await register(formData);
console.log(response);
if (response.status === false) return;
else {
loginWithJwt(response.headers["x-auth-token"]);// log the user in using jwt token
console.log(response.headers["x-auth-token"]);
navigate("/chatroom");
}
} catch (ex) {
console.log(ex.message);
}
};

multiple responses Cannot set headers after they are sent to the client

I wrote the following signup function.
It works fine with Postman but when I've added the code between "//send the cookie with the token" and "// end", I got this error message : "(node:11748) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client".
From what I saw here in stackoverflow, this error occurs because of multilple res. but I can't find how to rearrange the code so that I avoid this error.
exports.signup = async(req, res) => {
const { firstName, familyName, email, password, role } = req.body;
console.log("image", req.file);
try {
const user = await User.findOne({ attributes: ['email'], where: { email: email } });
if (user) {
fs.unlinkSync(req.file.path);
return res.status(409).send('This email already exists!');
} else {
const hashPass = await bcrypt.hash(password, 10);
const userObject = {
firstName: firstName,
familyName: familyName,
email: email,
password: hashPass,
role: role,
photoUrl: req.file ? `${req.protocol}://${req.get('host')}/images/${req.file.filename}` : null,
};
console.log("photo", userObject.photoUrl);
console.log("userObject", userObject);
const createdUser = await User.create(userObject);
const newToken = jwt.sign({ userId: user.id },
process.env.COOKIE_KEY, { expiresIn: "24h" }
);
const newCookie = { token: newToken, userId: createdUser.id };
const cryptedToken = cryptojs.AES.encrypt(JSON.stringify(newCookie), process.env.COOKIE_KEY).toString();
res.cookie('snToken', cryptedToken, {
httpOnly: true,
maxAge: 86400000 // 24h
});
res.status(200).send({ message: 'The user is successfully connected!', data: createdUser, cryptedToken: cryptedToken });
}
} catch (error) {
return res.status(500).send({ error: 'An error has occured while trying to sign up!' });
}
}
It's not related to "multiple request". In a successful case (also on error), you wrote status more than once.
for example in a successful case:
first you returned 201 after creating the user (which returned a response to the client)
res.status(201).send({message: User ${req.userObject.firstName} ${req.userObject.familyName} was registred }, createdUser);
and then
and here you tried to send another response at the end of the request
res.status(200).send({ message: 'The user is successfully connected!', data: user, cryptedToken: cryptedToken });
and you got the right error Cannot set headers after they are sent to the client, since it already returned 201 after Creation.
try to do something like:
try {
const hashPass = await bcrypt.hash(password, 10);
const userObject = { firstName: firstName,
familyName: familyName,
email: email,
password: hashPass,
role: role,
photoUrl: req.file ? `${req.protocol}://${req.get('host')}/images/${req.file.filename}` : null,
};
console.log("photo", userObject.photoUrl);
console.log("userObject", userObject);
const createdUser = await User.create(userObject);
//send the cookie with the token
const newToken = jwt.sign({ userId: user.id },
process.env.COOKIE_KEY, { expiresIn: "24h" }
);
const newCookie = { token: newToken, userId: user.id };
const cryptedToken = cryptojs.AES.encrypt(JSON.stringify(newCookie), process.env.COOKIE_KEY).toString();
res.cookie('snToken', cryptedToken, {
httpOnly: true,
maxAge: 86400000 // 24h
});
res.status(200).send({ message: 'The user is successfully connected!', data: user, cryptedToken: cryptedToken });
}
catch (error) {
return res.status(500).send({ error: 'An error has occured while trying to sign up!' });
}

Redirect from '/user/login' to '/user/me' in express with JWT and auth middleware

I'm trying to redirect a user to their profile after logging them in. What I'm trying to do is when they log in, we will find the user by credentials and then generate and auth token from them (note I created a user const for testing purposes). After both are done, I'll set a header Authorization, use the token, and pass it to the /user/me route. Here are my routes:
(login POST route, "/user/login"):
router.post('/user/login', async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
const token = await user.generateAuthToken()
res.header('Authorization', 'Bearer '+token)
res.status(302).redirect('/user/me')
} catch (err) {
res.status(400).send(err)
}
})
(profile route: "/user/me"):
router.get('/user/me', auth, async (req, res) => {
res.send(req.user)
})
(the "auth" middleware that I'm passing in the previous method):
const auth = async (req, res, next) => {
try{
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, SECRET_TOKEN)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token})
console.log(token)
if(!user) {
throw new Error("User not found")
}
req.token = token
req.user = user
next()
} catch(err) {
res.status(503).send({error: 'Please authenticate'})
}
}
But whenever I try this, it gives my 503 error from the auth method:
{
"error": "Please authenticate"
}
The Authorization header passes correctly as I've seen in my dev tools.
For more information, here's what the generateAuthToken & findByCredentials methods look like:
userSchema.methods.generateAuthToken = async function() {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, SECRET_TOKEN)
user.tokens = user.tokens.concat({token})
await user.save()
return token
}
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({email})
if(!user) {
throw new Error({message: "Unable to log in."})
}
const isValid = await bcrypt.compare(password, user.password)
if(!isValid) {
throw new Error({message: "Unable to log in."})
}
return user
}
For more more information, here's what my User model looks like:
const userSchema = mongoose.Schema({
email:{
type: String,
required: true,
unique: true,
trim: true,
validate(value) {
if(!validator.isEmail(value)) {
throw new Error("Not a valid email")
}
}
},
password:{
type: String,
required: true,
validate(value) {
if(value === "password") {
throw new Error("Enter a strong password.")
}
if(value.length < 8) {
throw new Error("Enter minimum 8 letters for a password.")
}
}
},
tokens: [{
token:{
type: String,
required: true
}
}]
})
I've solved it by using cookies. It maybe a temporary work-around but I'll find resources to make it more secure!
In the login route:
res.cookie('Authorization', `Bearer ${token}`, {
maxAge: 60000,
})
In the auth middleware:
const token = req.cookies['Authorization'].replace('Bearer ', '')

How to add a new key and value to existing document in MongoDB

I want to add the refreshtoken to the user info when the user logs in. So I can get the refreshtoken and pass it in my cookies.
Code:
router.post("/login", async (req, res) => {
const user = await User.findOne({ email: req.body.email });
if (!user) res.status(400).send({ msg: "User does not exists" });
const validPass = await compare(req.body.password, user.password);
if (!validPass) {
return res.status(400).json({ msg: "email or password is incorrect" });
}
const accesstoken = createAccessToken(user._id);
const refreshtoken = createRefreshToken(user._id);
//create different versions of the refresh token
// put the refreshtoken in the database
User.update(
{ id: user._id },
{
$set: {
refreshtoken: refreshtoken,
},
},
);
let userToken = user.refreshtoken;
userToken = refreshtoken;
//send token. Refreshtoken as a cookie and accesstoken as a regular
//response
//YOU HAVE ALREADY SAID IN THE SENDACCESTOKEN FUNCTION THAT YOU WOULD SEND THE MAIL ALSO
sendRefreshToken(res, refreshtoken);
sendAccessToken(req, res, user._id, accesstoken);
});

Resources