Cannot save User in session data - node.js

I am creating a MERN-dashboard, with a login, registration & a dashboard where only logged in Users have access to. Now I've managed to get the user registration and login working, however I seem to be missing something when it comes to saving the User in the express-session.
This is what I use for the login
app.post('/login', async (req, res) => {
const username = req.body.username;
const password = req.body.password;
const newUser = await User.findOne( {
username: username,
})
if (newUser) {
bcrypt.compare(password, newUser.get("passwd"), (error, result) => {
if (result) {
console.log(newUser)
req.session.user = newUser
req.session.loggedIn = true
res.send({newUser, message: 'Successful Login!'})
} else {
res.send({message: 'Wrong password!'})
}
})
} else {
res.send({message: 'User not found.'})
}
})
And this is how my frontend is checking if a User is logged in
app.get('/login', (req, res) => {
if (req.session.user) {
res.send({loggedIn: true, user: req.session.user})
} else {
res.send({loggedIn: false})
}
})
Now, if I log myself in the POST request signals me, that all is fine. However if I reload the page, the GET request tells me I am not logged in.
I have tried reading several articles about express-session but did not manage to find the solution to my problem.
Thank you in advance.

Related

User sessions data does not persist when calling from a different route

My user session does not persist within the server. I can see within the log that I saved it in my /login route, but when I try to access it from a different route, its "undefined".
My /login route:
app.route("/login")
.post(async (req, res) => {
var username = req.body.username,
password = req.body.password;
console.log('\t we are here')
try {
var user = await User.findOne({ username: username }).exec();
if(!user) {
res.redirect("/login");
}
user.comparePassword(password, (error, match) => {
if(!match) {
console.log('Password Mismatch');
console.log('Ensure redirect to /login');
res.redirect("/login");
}
});
req.session.user = user;
console.log('\t\treq.session:');
console.log(req.session)
var redir = { redirect: "/dashboard" };
return res.json(redir);
} catch (error) {
console.log(error)
}
});
In the above snippet I try to save the session data by req.session.user = user;. Its log appears as:
But now when I try to call the session I just stored, it shows "undefined". This is my /dashboard route & its corresponding log:
app.get("/dashboard", (req, res) => {
console.log(req.session.user_sid);
// console.log(req.cookies.user_sid);
if (req.session.user && req.cookies.user_sid) {
// res.sendFile(__dirname + "/public/dashboard.html");
console.log(req.session);
res.send("send something")
} else {
res.send("go back to /login");
}
});
To my understanding, user authentication is done my checking sessions and cookies, which is why I'm trying to save the session to request.session. I want to the data to persist so that I can use it in all my other routes such as when calling /dashboard api.
Dashboard api will be call by a protected route like when the user is logged in.

api call getting affected by another api call's validation

not really sure if my title is correct but my problem is that I have this reset password token checker in my api that seems to get affected by another api that finds a specific user, this api has user validation.
Here is what they look like:
//get specific user
router.get('/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});
//reset-password token check
router.get('/reset-pass', async (req, res) => {
await User.findOne({
where: {
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: {
[Op.gt]: Date.now()
}
}
}).then(user => {
if(!user) {
res.status(401).json({ error: 'Password reset link is invalid or has expired.'})
} else {
res.status(200).send({
username: user.username,
message: 'Password reset link Ok!'
});
}
});
});
then here is the validateToken
const validateToken = (req, res, next) => {
const accessToken = req.cookies['access-token'];
if (!accessToken)
return res.status(401).json({error: 'User not authenticated!'});
try {
const validToken = verify(accessToken, JWT_SECRET)
req.user = validToken;
if(validToken) {
req.authenticated = true;
return next();
}
} catch(err) {
res.clearCookie('access-token')
return res.status(400).json({error: err}).redirect('/');
}
};
when I comment out the get specific user api the reset password token check works. If I remove validateToken it returns null instead of giving me the username and message.
One of the things I notice is the route param "/:id", that means that literally everything would be processed by get specific user because all routes start with "/", only use params in routes with a prefix like "/user/:id" that way only the routes that starts with "/user" will execute that code.
Change your code to:
//get specific user
router.get('/user/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});

How to redirect based on user role with PassportJS?

I need to redirect 3 types of users (based on a single user model schema). How can I do this within my router.post() call in the users route? I understand that passport.authenticate takes in certain parameters but I am wondering if there is any way around it to have multiple redirects based on the user type (role in the schema)? Many thanks!
here is how I am doing it for one type of users:
//////this is my passport.js file//////
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
// Load User model
const User = require('../models/User');
module.exports = function(passport) {
passport.use(
new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
// Match user
User.findOne({
email: email
}).then(user => {
if (!user) {
return done(null, false, { message: 'That email is not registered' });
}
// Match password
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Password incorrect' });
}
});
});
})
);
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
};
/////////auth.js file/////////
module.exports = {
ensureAuthenticated: function(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
req.flash('error_msg', 'Please log in to view that resource');
res.redirect('/users/login');
},
forwardAuthenticated: function(req, res, next) {
if (!req.isAuthenticated()) {
return next();
}
}
};
//////users.js route bellow/////////
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const passport = require('passport');
// Load User model
const User = require('../models/User');
const { forwardAuthenticated } = require('../config/auth');
const mongoose = require('mongoose');
// Login Page
router.get('/login', forwardAuthenticated, (req, res) => res.render('login'));
// Register Page
router.get('/register', forwardAuthenticated, (req, res) => res.render('register'));
// Register
router.post('/register', (req, res) => {
const { role, name, email, password, password2 } = req.body;
let errors = [];
if (!role || !name || !email || !password || !password2) {
errors.push({ msg: 'Please enter all fields' });
}
if (password != password2) {
errors.push({ msg: 'Passwords do not match' });
}
if (password.length < 6) {
errors.push({ msg: 'Password must be at least 6 characters' });
}
if (errors.length > 0) {
res.render('register', {
errors,
role,
name,
email,
password,
password2
});
} else {
User.findOne({ email: email }).then(user => {
if (user) {
errors.push({ msg: 'Email already exists' });
res.render('register', {
errors,
role: role,
name,
email,
password,
password2
});
} else {
const newUser = new User({
role,
name,
email,
password
});
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then(user => {
req.flash(
'success_msg',
'You are now registered and can log in'
);
res.redirect('/users/login');
})
.catch(err => console.log(err));
});
});
}
});
}
});
// Login
router.post('/login', (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/aboutus',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
});
// Logout
router.get('/logout', (req, res) => {
req.logout();
req.flash('success_msg', 'You are logged out');
res.redirect('/users/login');
});
module.exports = router;
I am pretty sure that on post you can do something like this :
app.post('/login', (req, res) => {
if (req.body.role === 'normalUser') {
res.redirect('/normalUserPage')
} else if (req.body.role === 'administrator') {
res.redirect('/administratorPage');
} else if (req.body.role === 'receptionist') {
res.redirect('/receptionistPage');
}
});
I am pretty sure that this should do, make sure that these conditions come after trying to
sign in with the email and password provided.
What you pretty much have to do is set a condition for the three users, and based on the user type you can redirect them to their specific page.
another solution is to log in with the provided information and display the same page. However, based on the users' role, you can specify the content they are viewing.
example :
if the user type is admin, expose a div that allows them to view all the data with the ability to edit or delete.
if the user is normal, expose a div that allows them to view only the content specified for them. For instance only the names without any other information.
if the user is a receptionist expose a div that allows them to view all the content but without the ability to edit any information.
I really hope this helps you. let me know if this does help you.
If it does not help you I can try to figure out another solution.
This is an old thread, but for those that stumble across it...I had the same issue and this is how I addressed it:
In users.js route, include
router.get('/redirectLogin', (req, res) => {
if(req.user.role === role){ //replace role with whatever you're checking
res.render('dashboardOne.ejs')
} else {
res.render('dashboardTwo.ejs')
}
})
Then in the login POST method, change successRedirect to:
// Login
router.post('/login', (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/users/redirectLogin',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
});
Remember that passport.deserializeUser() attaches the user object to req.user, which allows you to access any attributes of that object.

My app works perfectly locally, all routes, with no issues. But on Heroku, all employees routes are getting a 503 service unavailable

I'm using passport authentication on a local database for two kind of users, admin and employees. The whole app works well locally but on Heroku, employees cannot be signed up or signed in. It's giving me a 503 unavailable service error. Any clue?
I have tried postman and I'm getting the same reponse after 30+ seconds.
// Register
router.post('/employees/signup', (req, res) => {
console.log(req.body);
const { firstName, lastName, email, password, password2, jobType } =
req.body;
if (password.length < 6) {
throw 'Password must be at least 6 characters';
}
else {
Employee.findOne({
where: {
email
}
}).then(employee => {
if (employee) {
res.send("Email already exists!")
} else {
const encryptedPassword = bcrypt.hashSync(password, salt);
let newEmployee = {
firstName,
lastName,
email,
password: encryptedPassword,
jobType,
};
Employee.create(newEmployee)
.then(() => {
delete newEmployee.password;
res.send(newEmployee)
})
.catch(function (err) {
console.log(err);
res.json(err);
});
}
});
}
});
// Login
router.post("/employees/login", function (req, res, next) {
const { email, password } = req.body;
// generate the authenticate method and pass the req/res
passport.authenticate('employees-local', function (err, employee,
info) {
if (!email || !password) {
return
}
if (err) {
return res.status(401).json(err);
}
if (!employee) {
return res.status(401).json(info);
}
// req / res held in closure
req.logIn(employee, () => {
Employee.findOne({
where: {
email: req.body.email,
},
}).then(employee => {
const token = jwt.sign({ email: employee.email },
jwtSecret.secret, {
expiresIn: 60 * 60,
});
res.status(200).send({
authEmployee: true,
token,
message: 'user found & logged in',
employee
});
});
});
})(req, res, next);
});
//Getting all employees
router.get("/employees", (req, res) => {
Employee.findAll({}).then(function (dbEmployee) {
res.json(dbEmployee);
});
});
The problem came when some time ago I wanted to create a personal profile for the employees, so I had created a model for the employees profile. Then I had changed my mind but never deleted that model; so Sequelize kept trying to associate employees with the profiles model, therefore, the routes were not getting hit. It's been solved now by just deleting that old model, and I'm not getting that 503 anymore. Thanks a lot for your input as it helped me think from a different perspective and reach out to the problem the correct way and correct unnecessary lines of code.

Unauthorized after login with passport-jwt

I used passport-jwt for login. it find the user and the user can login but when after logging in, I want to redirect to the user dashboard nothing happens and it shows Unauthorized the in browser when it redirect to localhost:8080/my/
can you find out what the problem is:
p.s: when I remove passport.authenticate('jwt', {session: false}) from my.js it works and it redirects to /my/. so I think the problem is with this part of code or JWTStrategy in auth.js. I don't know how to fix it!
here's my code : (sorry it's to much code)
auth.js:
//Create a passport middleware to handle User login
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
}, async (username, password, done) => {
try {
//Find the user associated with the email provided by the user
await User.findOne({
username: username
}, async function (err, user) {
if (!user) {
//If the user isn't found in the database, return a message
return done(null, false, {
message: 'User not found'
});
}
//Validate password and make sure it matches with the corresponding hash stored in the database
//If the passwords match, it returns a value of true.
let validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, {
message: 'Wrong Password'
});
}
//Send the user information to the next middleware
return done(null, user, {
message: 'Logged in Successfully'
});
});
} catch (error) {
return done(error);
}
}));
passport.use(new JWTStrategy({
jwtFromRequest : ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey : 'secret_token'
}, function(jwt_payload, done) {
User.findOne({id: jwt_payload.sub}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
// or you could create a new account
}
});
}));
authenticate.js :
router.post('/login', async (req, res, next) => {
passport.authenticate('local', async (err, user, info) => {
try {
if (err || !user) {
const error = new Error('An Error occured')
return next(error);
}
req.login(user, {
session: false
}, async (error) => {
if (error) return next(error)
//We don't want to store the sensitive information such as the
//user password in the token so we pick only the email and id
const body = {
_id: user._id,
username: user.username
};
//Sign the JWT token and populate the payload with the user email and id
const token = jwt.sign({
user: body
}, 'top_secret');
//Send back the token to the user
// return res.json({
// token
// });
res.redirect('/my/?token='+token);
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
module.exports = router;
user.js
const mongoose = require('mongoose');
const Role = require('./role');
const bcrypt = require('bcrypt');
let Schema = mongoose.Schema;
let userSchema = new Schema({
.
.
.
});
userSchema.pre('save', async function(next){
//'this' refers to the current document about to be saved
const user = this;
//Hash the password with a salt round of 10, the higher the rounds the more secure, but the slower
//your application becomes.
const hash = await bcrypt.hash(this.password, 10);
//Replace the plain text password with the hash and then store it
this.password = hash;
//Indicates we're done and moves on to the next middleware
next();
});
userSchema.methods.isValidPassword = async function(password){
const user = this;
//Hashes the password sent by the user for login and checks if the hashed password stored in the
//database matches the one sent. Returns true if it does else false.
let compare= await bcrypt.compare(password , user.password);
return compare;
}
module.exports = mongoose.model('User' , userSchema);
my.js
//secure routes that only users with verified tokens can access.
//Lets say the route below is very sensitive and we want only authorized users to have access
//Displays information tailored according to the logged in user
router.get('/',passport.authenticate('jwt', {session: false}), (req, res) => {
//We'll just send back the user details and the token
res.json({
message : 'You made it to the secure route',
token : req.query.token
});
})
module.exports = router;
This problem usually occurs because of an invalid JWT. To authenticate a JWT with passport-jwt it has to be of the following format:
"JWT xxxx.xxxx.xxxx"
So maybe try changing the following line in your Authenticate.js file:
res.redirect('/my/?token='+token);
// CHANGE IT TO:
res.redirect('/my/?token=' + 'JWT ' + token);
I hope this solves your problem.

Resources