I am having an issue with my app with the req.user persisting. After a successful login/serializeUser etc, I can see the req.user in the saved, and the application works as desired for about 1-2 minutes. After that, the req.user clears to undefined. I'm using currently using react and calling a method to the server to confirm there is a req.user on componentDidMount. I have no idea why and I'm pretty new to this.
In my server.js:
app.use(bodyParser.json())
// Sessions
app.use(
express-session({
secret: 'feedmeseymour',
cookie: { maxAge: 60000 },
store: new MongoStore({ mongooseConnection: dbConnection }),
resave: false,
saveUninitialized: false
})
)
// MIDDLEWARE
app.use(morgan('dev'))
app.use(
bodyParser.urlencoded({
extended: false
})
)
app.use(bodyParser.json())
app.use(express.static('public'));
app.use(cors());
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
// Passport
app.use(passport.initialize())
app.use(passport.session())
My login route:
router.post(
'/',
function (req, res, next) {
console.log('Received login information. Username: ' + req.body.username)
const {errors, isValid } = validateLoginInput(req.body);
if (!isValid) {
return res.status(400).json(errors);
}
next()
},
passport.authenticate('local', {failWithError: true }),
function (req, res, next) {
console.log('req.user in the backend: ' + req.user);
var userInfo = req.user
res.send(userInfo);
},
function (err, req, res, next) {
res.status(401).send({ success: false, message: err })
}
)
passport.serializeUser/deserialize methods:
passport.serializeUser((user, done) => {
console.log('*** serializeUser called, user: ')
console.log(user) // the whole raw user object!
console.log('---------')
done(null, { _id: user._id })
})
// user object attaches to the request as req.user
passport.deserializeUser((id, done) => {
console.log('DeserializeUser called')
User.findOne(
{ _id: id },
'username',
(err, user) => {
console.log('*** Deserialize user, user:')
console.log(user)
console.log('--------------')
done(null, user)
}
)
})
called on componentDidMount:
getUser() {
axios.get('/users').then(response => {
if (response.data.user) {
this.setUser(true, response.data.username, response.data.super);
}
else {
console.log('no user is logged in')
this.setUser(false, null, false);
}
})
}
Which calls this route in the back:
router.get('/', (req, res, next) => {
console.log('req.user:');
console.log(req.user);
console.log('------------');
console.log('req.session:');
console.log(req.session);
console.log('------------');
if (req.user) {
User.findOne({ _id: req.user._id }, (err, user) => {
if (err) {
console.log('logged user retrieval error: ', err)
} else if (user) {
console.log('found user from _id: ' + user);
res.json({ user: req.user, super: user.super })
}
})
} else {
res.json({ user: null })
}
})
req.user exists in the back for about 1-2 minutes and then it goes to undefined. I am storing the user in a store in mongodb, and I can see the session still exists there too.
the req.user is saved with information. In a minute, this will change to undefined:
req.user:
{ _id: 5b7ded93525742053a6dd155, username: 'admin' }
------------
req.session:
Session {
cookie:
{ path: '/',
_expires: 2018-09-09T07:10:22.902Z,
originalMaxAge: 60000,
httpOnly: true },
passport: { user: { _id: '5b7ded93525742053a6dd155' } } }
------------
cookie: { maxAge: 60000 }
That's 60.000 milliseconds, or 60 seconds. Exactly the 1 minute you're describing.
Try storing the user in the session object of the request. This way it works for me.
Related
I'm trying to build an app with Node.js and Passport.js, but I don't want to continue if I can't nail down authentication. Right now I have passport-local set up and once I log in, by all accounts I should be getting an authenticated user on other routes. But I'm not. It looks like the session cookie being sent when logged in has the user info, but every other route sends a cookie that lacks the user info. I can't for the life of me figure out what I'm doing wrong.
here is how app is set up:
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
secure: false,
},
store: new FirestoreStore({
dataset: new Firestore(),
kind: "express-sessions",
}),
})
);
app.use(passport.initialize());
app.use(passport.session());
My route:
userRouter
.route("/api/v1/signup")
.post(jsonParser, async (req, res, next) => {
// console.log(req.body);
addUser(req.body);
res.status(200).json({ message: "success" });
});
userRouter.route("/api/v1/login").post(
jsonParser,
passport.authenticate("local", {
session: true,
}),
async (req, res, next) => {
res.status(200).json({ message: "success" });
}
);
and my passport.js
const LocalStrategy =
require("passport-local").Strategy;
const argon2 = require("argon2");
const {
getUserByEmail,
getUserById,
} = require("../database/users");
const initialize = (passport) => {
const authenticateUser = async (
email,
password,
done
) => {
const user = await getUserByEmail(email);
if (user === null) {
return done(null, false, {
message: "No user with that email",
});
}
// argon2.verify(user.email, password);
try {
const verifiedPassword =
await argon2.verify(
user.password,
password
);
if (verifiedPassword) {
// password match
console.log("success");
return done(null, user);
} else {
// password did not match
console.log("failure");
return done(null, false, {
message: "Password incorrect",
});
}
} catch (error) {
// internal failure
console.log(error, "error");
return done(error);
}
};
passport.use(
new LocalStrategy(
{
usernameField: "email",
},
authenticateUser
)
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
return done(null, getUserById(id));
});
};
module.exports = initialize;
When I login, my session gets this cookie:
Session {
cookie: {
path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: false
},
passport: { user: 1657255017610 }
}
On every other route when I want to check if the user is authenticated or not, I get this:
Session {
cookie: {
path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: false
}
}
I've seen this question posted else where, however, none of those solutions appeared to work for me. Upon trying to console log the req.session and req.user, I have a return of the session as shown below, however, the req.user returns as undefined. I believe there is an issue when serializing the user as VS code alerts me that the "'id' property does not exist on type user" in the passport.serialUser function, but it console logs user.id properly (the object id of my user document).
If anyone is aware of what might be cause of the user.id property not existing/the user being undefined I would appreciate it.
passport.js file:
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcrypt");
const User = require("../models/User");
const verifyCallback = (username, password, done) => {
User.findOne({ username: username })
.then((user) => {
if (!user) {
return done(null, false);
}
// Validate Password
bcrypt.compare(password, user.password).then((isMatch) => {
if (isMatch) {
return done(null, user);
} else {
return done(null, false);
}
});
})
.catch((err) => {
done(err);
});
};
const strategy = new LocalStrategy(verifyCallback);
passport.use(strategy);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((userId, done) => {
User.findById(userId)
.then((user) => {
done(null, user);
})
.catch((err) => done(err));
});
index.js file:
session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: true,
store: sessionStore,
cookie: {
maxAge: 1000 * 60 * 60 * 24,
},
})
);
// Passport Auth Middleware
const passportConfig = require("./config/passport");
// Initialize Passport and Use Session for Serialize/Deserialization
app.use(passport.initialize());
app.use(passport.session());
app.use((req, res, next) => {
console.log(req.session);
console.log(req.user);
next();
});
console log:
cookie: {
path: '/',
_expires: 2021-11-17T02:08:23.650Z,
originalMaxAge: 86400000,
httpOnly: true
}
}
undefined
User document example:
{"_id":{"$oid":"6186c13beb18d33d5088f7b2"},
"username":"coolguy9",
"password":"$2b$13$4p5apH8Q8k8hP4WpCNt6/O40M9I0jlkG.LXIE3d/V89Kmtmk1plxa",
"firstname":"Bob",
"lastname":"Woodhull",
"team":"Warehouse",
"createdAt":{"$date":{"$numberLong":"1636221243904"}},
"updatedAt":{"$date":{"$numberLong":"1636221243904"}},
"__v":{"$numberInt":"0"}}```
Solved the issue. In my loginAPI.js file on my front end, which contains the axios instance, I needed to include the option withCredentials: true.
Example:
baseURL: "http://localhost:8000/api/",
timeout: 1000,
withCredentials: true,
});
Alright, I've been racking my brain over this for hours now. When I call my 'sign-in' route the passport middleware works fine and returns with a req.user obj, but when I call another route after that, req.user for that other route is undefined. Where exactly have I messed up here? I'm not sure it matters, but I am calling my API routes from a react client.
auth
router.post(
"/sign-in",
passport.authenticate("local"),
async (req, res, next) => {
if (!req.user) console.log("NO USER!*******************");
try {
const user = _.get(req, "user", "");
res.status(200).json(user);
} catch (e) {
console.log({ e });
return res.status(400).json(false);
}
}
);
My Server
app.use(cors());
app.use(cookieparser());
app.use(logger("dev"));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(
session({
secret: "super",
resave: false,
saveUninitialized: true,
cookie: { secure: false, maxAge: 4 * 60 * 60 * 1000 }
})
);
require("./utils/passport");
app.use(passport.initialize());
app.use(passport.session());
./utils/passport
const _ = require("lodash");
const LocalStrategy = require("passport-local").Strategy;
const { PrismaClient } = require("#prisma/client");
const prisma = new PrismaClient();
const bcrypt = require("bcrypt");
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(async function(id, done) {
console.log("");
console.log("deserializeUser*************************");
console.log("");
try {
const user = await prisma.user.findOne({ where: { id } });
if (!user) {
return done(null, false);
}
return done(null, user);
} catch (e) {
done(e);
}
});
passport.use(
new LocalStrategy(
{
passReqToCallback: true,
usernameField: "email"
},
async (req, email, password, done) => {
try {
const user = await prisma.user.findOne({ where: { email } });
if (!user) {
return done(null, false);
}
await bcrypt.compare(
password,
_.get(user, "password", ""),
(err, result) => {
if (!result) {
done(null, false);
}
done(null, user);
}
);
} catch (e) {
done(e);
}
}
)
);
I suspect something is wrong with the portion where you have your post method with passport authenticate.
This portion may be deleted:
if (!req.user) console.log("NO USER!*******************");
I think the asynchronous may produce here odd results. Then for the sake of troubleshooting, include in your
const user = _.get(req, "user", "");
real data, such as real user data. Thus you will be able to verify that the data is passing through.
When I make a fetch request from my react frontend to login using passport.authenticate('./local), my passport.serializeUser is called but passport.deserializeUser is NOT (and req.user is not set).
I've read as many answers to this question on stackoverflow but to no avail. Here is my code below.
All this comes before my routes on the server.js:
//Express body parser
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.json());
//Express Session
app.use(session({
secret: 'secret',
resave: true,
saveUninitialized: true,
cookie: {
secure: false
}
}));
// Passport config
require('./config/passport')(passport);
//Passport Middleware
app.use(passport.initialize());
app.use(passport.session());
Here is the passport config file:
module.exports = function(passport) {
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, async function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
const match = await bcrypt.compare(password, user.password);
if (!match) { return done(null, false); }
return done(null, user);
});
}
));
passport.serializeUser(function(user, done) {
console.log('this gets called logged)
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log('this does NOT GET LOGGED');
User.findById(id, function(err, user) {
done(err, user);
});
});
};
Here is the react fetch request for the login route:
fetch('http://localhost:5000/authentication/login', {
method: 'POST',
body: JSON.stringify(this.state.formData),
headers: {"Content-Type": "application/json"},
mode: 'cors'
})
.then(res => res.json())
.then(resObject => {
if (resObject.errors) {
console.log('errors')
} else {
this.props.dispatch(handleLoginuser(resObject.user))
this.setState({
redirect: true
})
}
});
Here is the fetch request for another random route that should be protected:
componentDidMount() {
fetch('http://localhost:5000/protectedroute', {
method: 'GET',
mode: 'cors',
credentials: 'include'
})
.then(res => res.json())
.then(resObject => {
if(resObject.loggedIn) {
this.setState({
loggedIn: true
})
}
});
}
Here are the login and protected routes:
app.post('/authentication/login', passport.authenticate('local'), (req, res) => {
res.json({errors: false, user: req.user})
});
app.route('/protected')
.get(function(req, res) {
//req.user will be undefined!
if (req.user) {
return res.json({loggedIn: true})
} else {
return res.json({loggedIn: false})
}
})
I believe it's because of your post/login route on the back end.
Calling the passport.authenticate middleware does not complete the login process unless you use the boilerplate code from the official docs (which does not work with React)
You need to call req.login to complete the login process. Check out this example
app.post('/authentication/login', (req, res, next) => {
passport.authenticate('local', (err, theUser, failureDetails) => {
if (err) {
res.status(500).json({ message: 'Something went wrong authenticating user' });
return;
}
if (!theUser) {
res.status(401).json(failureDetails);
return;
}
// save user in session
req.login(theUser, (err) => {
if (err) {
res.status(500).json({ message: 'Session save went bad.' });
return;
}
console.log('---123456789098765432345678---', req.user);
res.status(200).json({{errors: false, user: theUser}});
});
})(req, res, next);
});
Setting up withCredentials: true while sending the post request worked for me.
axios.post(uri, {
email: email,
password: password
}, {
withCredentials: true
})
I am reading an article about nodejs express module and sessions here
https://www.codementor.io/emjay/how-to-build-a-simple-session-based-authentication-system-with-nodejs-from-scratch-6vn67mcy3
I am confused on this portion of the code
app.use((req, res, next) => {
if (req.cookies.user_sid && !req.session.user) {
res.clearCookie('user_sid');
}
next();
});
from where did the req object get the user property ?
If we look at the article the session object is created like
app.use(session({
key: 'user_sid',
secret: 'somerandonstuffs',
resave: false,
saveUninitialized: false,
cookie: {
expires: 600000
}
So how does this even work
if (req.cookies.user_sid && !req.session.user)
how does the req.cookies.user_id shouldnt it be req.cookies.key ?
How does req.session.user work ?
user is not even a property of the session object no ?
Look further down in the article. There are several instances where req.user is an lvalue. In fact it's entirely up to you to assign to it:
The article posits this code block:
Signup:
// route for user signup
app.route('/signup')
.get(sessionChecker, (req, res) => {
res.sendFile(__dirname + '/public/signup.html');
})
.post((req, res) => {
User.create({
username: req.body.username,
email: req.body.email,
password: req.body.password
})
.then(user => {
/*
* The user just signed up, so let's sign him in
*/
req.session.user = user.dataValues;
res.redirect('/dashboard');
})
.catch(error => {
res.redirect('/signup');
});
});
Logging In:
// route for user Login
app.route('/login')
.get(sessionChecker, (req, res) => {
res.sendFile(__dirname + '/public/login.html');
})
.post((req, res) => {
var username = req.body.username,
password = req.body.password;
User.findOne({ where: { username: username } }).then(function (user) {
if (!user) {
res.redirect('/login');
} else if (!user.validPassword(password)) {
res.redirect('/login');
} else {
/*
* The user just logged in so lets sessionize them
*/
req.session.user = user.dataValues;
res.redirect('/dashboard');
}
});
});