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
})
Related
I am creating authentication with passportjs local strategy. So I did how it' is supposed to be done. But I don't know if something I am missing here or not.
Problem: After submitting the data from Vue 3 App a cookie supposed to be set in the browser to make sure that the user is
authenticated. Here the problem is the cookie is not being saved in
the browser. I tried the same thing with React app and that worked
Github Link: https://github.com/abeertech01/vue-cookie-problem-resolve
Here we have all the code.
Backend (server.js):
mongoose
.connect("mongodb://localhost:27017/local-auth")
.then((res) => console.log(`MongoDB is connected`))
.catch((err) => console.log("error: ", err))
//Middleware
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(
cors({
origin: ["http://localhost:3000", "http://127.0.0.1:5173"],
credentials: true,
})
)
app.use(
session({
secret: "secretcode",
resave: true,
saveUninitialized: true,
})
)
app.use(cookieParser("secretcode"))
app.use(passport.initialize())
app.use(passport.session())
require("./passportConfig")(passport)
//Route
app.post("/login", (req, res, next) => {
passport.authenticate("local", (err, user, info) => {
if (err) throw err
if (!user) res.send("No User Exists")
else {
req.logIn(user, (err) => {
if (err) throw err
res.send("Successfully Authenticated")
console.log("50 req.user", req.user)
})
}
})(req, res, next)
})
I did the passport things in a separate file and that is here in passportConfig,js:
const User = require("./user")
const bcrypt = require("bcryptjs")
const localStorage = require("passport-local").Strategy
module.exports = function (passport) {
passport.use(
new localStorage((username, password, done) => {
User.findOne({ username: username }, (err, user) => {
if (err) throw err
if (!user) return done(null, false)
bcrypt.compare(password, user.password, (err, result) => {
if (err) throw err
if (result === true) {
return done(null, user)
} else {
return done(null, false)
}
})
})
})
)
passport.serializeUser((user, cb) => {
cb(null, user.id)
})
passport.deserializeUser((id, cb) => {
User.findOne({ _id: id }, (err, user) => {
const userInformation = {
username: user.username,
}
cb(err, userInformation)
})
})
}
Now in frontend I used axios and the following is the frontend is in the following:
const login = () => {
axios({
method: "POST",
data: {
username: "abeer",
password: "123",
},
withCredentials: true,
url: "http://localhost:4000/login",
}).then((res) => console.log(res))
}
This is in the <script setup></script> tag. In the template tag I ued the function like this: <button #click="login">Login</button>
I am trying to login an user (by a .js file, not via postman or browser) and maintain the session throughout the code. The login function seems to work fine, passport.authenticate, passportConfig and serializeUser get called, the response status is 200 OK, but when I try to call a function like sendFunds, the response is 'User is not authenticated'.
It works if I post the requests via postman. It seems like the login request gets a cookie and sends it automatically with the sendFunds request.
I guess that I need to do the same in my accountGenerator.js file, that means call the login method, get the cookies and send them with the sendFunds request, but I can't figure it out. How do I get them and do I need to manually add them to the express-session ?
Please :)
accountGenerator.js
async function loginUser(userLogin) {
return post('http://localhost:3002/api/user/login', userLogin)
}
function sendFunds(transferDetails) {
post('http://localhost:3002/api/user/sendFunds', transferDetails)
.then((res) => {
console.log(`Status: ${res.status}`);
}).catch((err) => {
console.error(err);
});
}
const loginResponse = await loginUser(userLogin);
export function loginUser(req, res) {
if (req.isAuthenticated()) {
res.status(200).send({
message: 'User is authenticated'
});
return;
}
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/error"
// failureRedirect: "/login"
})(req, res, next);
}
export function sendFunds(req, res) {
if (!req.isAuthenticated()) {
res.status(401).send({
message: 'User is not authenticated'
});
return;
}
req.body.secretKey = AUTHENTICATOR_SECRET_KEY;
post(SEND_FUNDS_API_URL, req.body)
.then((response) => {
res.status(200).send(response.data);
}).catch((err) => {
console.error(err);
res.status(500).send(err);
});
}
export function passportConfig() {
passport.use('local', new LocalStrategy(
async (username, password, done) => {
const response = await User.findOne({ name: username });
if (!response) {
return done(null, false, { message: 'Incorrect username.' });
}
const isValidPassword = await compare(password, response.password);
if (!isValidPassword) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, response);
}
))
}
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user)
})
})
app.use(expressSession({
store: new MongoStore({
mongoUrl: 'mongodb://127.0.0.1:27017/accountBankApp',
// mongooseConnection: mongoose,
ttl: 1 * 24 * 60 * 60, // = 365 days.
}),
secret: 'secret',
resave: true,
saveUninitialized: true,
}));
What worked for me was combining express-session with cookie-parser, even if cookie-parser is not required anymore in order for express-session to work.
Since version 1.5.0, the cookie-parser middleware no longer needs to be used for this module to work. This module now directly reads and writes cookies on req/res. Using cookie-parser may result in issues if the secret is not the same between this module and cookie-parser.
I am using passport local strategy to authenticate the users, here is my login code:
app.post("/login",function(req,res){
const user = new model({
username:req.body.username,
password:req.body.password,
});
req.login(user, function(err) {
if (err) {
res.render("login",{error: err});
} else {
passport.authenticate("local")(req, res, function() {
res.redirect("/dashboard");
});
}
});
});
Now if I enter an incorrect password then an unauthorized message comes and then if I go to my dashboard route then req.isAuthenticated() is true,
here is my dashboard code:
app.get("/dashboard",function(req,res){
if(req.isAuthenticated()){
//mywork
}
How to solve this problem and how/where to handle that unauthorized message?
passport.use(model.createStrategy());
passport.serializeUser(model.serializeUser());
passport.deserializeUser(model.deserializeUser());
and
app.use(session({
secret: "secret",
resave: false,
saveUninitialized: false,
}));
You're using req.login. Do you know what it does? Here is how you handle you'r issues, first you create a strategy ( obviously you have a user model ).
const User = require('../models/User');
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
/**
* Sign in using Email and Password.
*/
passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
User.findOne({ email: email.toLowerCase() }, (err, user) => {
if (err) { return done(err); }
if (!user) {
return done(null, false, { msg: `Email ${email} not found.` });
}
if (!user.password) {
return done(null, false, { msg: 'Your account was registered using a sign-in provider. To enable password login, sign in using a provider, and then set a password under your user profile.' });
}
user.comparePassword(password, (err, isMatch) => {
if (err) { return done(err); }
if (isMatch) {
return done(null, user);
}
return done(null, false, { msg: 'Invalid email or password.' });
});
});
}));
Then in your controller you can create a login method:
/**
* POST /login
* Sign in using email and password.
*/
exports.postLogin = (req, res, next) => {
const validationErrors = [];
if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' });
if (validator.isEmpty(req.body.password)) validationErrors.push({ msg: 'Password cannot be blank.' });
if (validationErrors.length) {
req.flash('errors', validationErrors);
return res.redirect('/login');
}
req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false });
passport.authenticate('local', (err, user, info) => {
if (err) { return next(err); }
if (!user) {
req.flash('errors', info);
return res.redirect('/login');
}
req.logIn(user, (err) => {
if (err) { return next(err); }
req.flash('success', { msg: 'Success! You are logged in.' });
res.redirect(req.session.returnTo || '/');
});
})(req, res, next);
};
To make sure you'r routes are authenticated:
app.get('/', homeController.index);
app.get('/login', userController.getLogin);
app.post('/login', userController.postLogin);
app.get('/logout', userController.logout);
app.get('/forgot', userController.getForgot);
app.post('/forgot', userController.postForgot);
app.get('/reset/:token', userController.getReset);
app.post('/reset/:token', userController.postReset);
app.get('/signup', userController.getSignup);
app.post('/signup', userController.postSignup);
app.get('/account/verify', passportConfig.isAuthenticated, userController.getVerifyEmail);
app.get('/account/verify/:token', passportConfig.isAuthenticated, userController.getVerifyEmailToken);
app.get('/account', passportConfig.isAuthenticated, userController.getAccount);
And your app settings for passport strategy session:
app.use(session({
resave: true,
saveUninitialized: true,
secret: process.env.SESSION_SECRET,
cookie: { maxAge: 1209600000 }, // two weeks in milliseconds
store: new MongoStore({
url: process.env.MONGODB_URI,
autoReconnect: true,
})
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
app.use((req, res, next) => {
if (req.path === '/api/upload') {
// Multer multipart/form-data handling needs to occur before the Lusca CSRF check.
next();
} else {
lusca.csrf()(req, res, next);
}
});
app.use(lusca.xframe('SAMEORIGIN'));
app.use(lusca.xssProtection(true));
app.disable('x-powered-by');
app.use((req, res, next) => {
res.locals.user = req.user;
next();
});
app.use((req, res, next) => {
// After successful login, redirect back to the intended page
if (!req.user
&& req.path !== '/login'
&& req.path !== '/signup'
&& !req.path.match(/^\/auth/)
&& !req.path.match(/\./)) {
req.session.returnTo = req.originalUrl;
} else if (req.user
&& (req.path === '/account' || req.path.match(/^\/api/))) {
req.session.returnTo = req.originalUrl;
}
next();
});
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.
I've got an Express server using Passport for auth and a React frontend.
When I post a login request, it works; req.session console logs as:
cookie:
{ path: '/',
_expires: 2018-02-25T01:06:21.555Z,
originalMaxAge: 14400000,
httpOnly: true,
secure: false },
passport: { user: 'test' }
('test' is in fact the username I'm using). For now I'm just trying to debug this using a test route and a simple get request:
fetch('/api/auth/test', { credentials: 'include' })
I console log req.session and passport is gone:
cookie:
{ path: '/',
_expires: 2018-02-25T01:06:26.596Z,
originalMaxAge: 14400000,
httpOnly: true,
secure: false }
Here's my Passport code:
In my route:
authRoutes.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) { return next(err); }
if (!user) { return res.json({ message: info }); }
req.logIn(user, (err) => {
if (err) { return next(err); }
return res.json({ message: 'ok', user: user.username });
});
})(req, res, next);
});
local.js:
const options = {};
init();
passport.use(
new LocalStrategy(options, (username, password, done) => {
User.findByUsername(username)
.then((user) => {
if (!user) {
return done(null, false, 'badUser');
}
if (!authHelpers.comparePass(password, user.password)) {
return done(null, false, 'badPw');
}
return done(null, user);
})
.catch(err => done(err));
})
);
I've been working on this for hours and can't figure it out. Any ideas?