Passport deserializeUser() is never called with Axios http request - javascript

When I send a request via axios to my Express app, req.isAuthenticated() is always false and req.user does not exist, even after logging in. But when I send a request via Postman to the app, it works. It seems that deserializeUser() is never called so the req.session.passport field is never populated.
I've tried all of the suggestions online, any help is appreciated.
External request:
async tweet(content) {
try {
await axios.post(this.url + '/tweets/new', {
content: content,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
}
});
} catch (err) {
console.log(err);
}
}
index.js
const passport = require('passport');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const RedisStore = require('connect-redis')(session)
const redisCookie = require('heroku-redis-client');
require('./config/passport')(passport);
// required for passport
app.use(cookieParser());
app.use(session({
// secret: process.env.SECRET || 'enteryoursecrethere',
secret: 'enteryoursecrethere',
cookie: { maxAge: 3600000 },
resave: true,
store: new RedisStore({client: redisCookie.createClient()}),
saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
next();
});
router.js
var tweets = require('../controllers/tweets');
var router = express.Router();
var isLoggedIn = require('../middleware/isLoggedIn');
router.post('/tweets/new', isLoggedIn, tweets.tweet);
middleware/isLoggedIn.js
module.exports = (req, res, next) => {
// If user is authenticated in the session, carry on.
if (req.isAuthenticated()) {
next();
return
}
// If they aren't redirect them to the home page.
res.redirect('/');
}
passport.js
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models').User;
const Sequelize = require('sequelize');
module.exports = function(passport) {
// The login request establishes a session maintained in a browser cookie.
// Requests after the login request not contain credentials,
// but rather the unique cookie that identifies the session. The user object
// is constructed to and from the ID in the cookie.
// Converts user to user id.
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// Converts user id to user, stored in req.user.
passport.deserializeUser(function(id, done) {
User.findById(id).then(function(user) {
done(null, user);
}).catch(function(err) {
done(err);
});
});
/* ============Login============ */
passport.use('local-login', new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback : true // Send entire request for flash message.
}, loginCallback));
passport.use('local-signup', new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback : true
}, signupCallback));
};
function loginCallback(req, username, password, done) {
if (req.isAuthenticated()) {
return done(null, req.user);
}
// Look up the user by username.
User.findOne({
where: {
username: username
}
}).then(function(user) {
if (!user) {
return done(null, false, req.flash('loginUsernameMessage', 'Wrong username.'));
}
if (!user.validatePassword(password)) {
return done(null, false, req.flash('loginPasswordMessage', 'Wrong password.'));
}
return done(null, user.get());
}).catch(function(err) {
return done(err);
});
}
function signupCallback(req, username, password, done) {
// Asynchronous. User.findOne wont fire unless data is sent back.
process.nextTick(function() {
if (password != req.body.password_confirm) {
return done(null, false, req.flash('signupMessage', 'Passwords don\'t match.'));
}
// Find a user whose email is the same as the forms email.
// We are checking to see if the user trying to login already exists.
User.findOne({
where: {
[Sequelize.Op.or]: [ { username: username }, { email: req.body.email }]
}
}).then(function(user) {
// Check to see if theres already a user with that username or email.
if (user) {
return done(null, false, req.flash('signupMessage', 'That email or username is already taken.'));
}
// Create the user.
var data = {
fname: req.body.fname,
lname: req.body.lname,
username: username,
email: req.body.email,
password: User.generateHash(password)
}
User.create(data).then(function(newUser) {
return done(null, newUser);
}).catch(function(err) {
return done(err);
});
}).catch(function(err) {
return done(err);
});
});
}

It works only with postman because the session is being set at the server(express) but not at the client(axios).
So when you request from axios, It doesn't know if the session is already set at the server. So you need to send the credentials origin along with the request. Modify your request like this:
await axios.post(this.url + '/tweets/new', {
content: content,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
credentials: "same-origin"
});
Refer this for more info.

This question is a year old now but I had a heck of a time solving this problem using Node/Express/Passport/React with a local-strategy and express-session.
For me, adding this to the header:
withCredentials:true
wasn't enough. That did trigger a CORS problem but did not actually make axios send the session cookie with the request.
This causes a situation where the login would create a session, but the deserialize function would never be called and req.user would always be empty.
This is what fixed the issue:
In my React component constructor I added this line of code:
axios.defaults.withCredentials = true;
That fixed it.
In fact, it fixed it so well that I was able to remove all other headers in Axios. Everything just started working.
Figuring this out took me a solid two hours today, I hope this answer helps someone.

Related

Missing Credentials while using Passport on Node JS and Express

I'm working on a project developing an API. The thing is I'm working with Passport and there's this thing that throws me an error of "Missing Credential". I've searched everywhere and I don't know what to do.
Part of Index.js
app.use(express.static(publicc));
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(passport.initialize());
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: false
}));
app.use(passport.session());
app.use(cors());
app.use(morgan('dev'));
Passport.js with the SignUp Function
passport.use('signup', new localStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, async (email, password, done) => {
try {
console.log('si')
const user = await findUser(email);
if (user.length > 0) {
return done(null, false, { message: 'User already exists' });
}
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
const newUser = {
user_name: req.body.user_name,
user_role: req.body.user_role,
email: email,
user_password: hashedPassword
};
console.log(newUser);
const result = await createUser(newUser);
return done(null, newUser);
}
catch (error) {
console.log('si')
done(error);
}
}
));
Controller with the SingUp
const postSignup = async (req, res, next) => {
passport.authenticate('signup', { session: false }, async(err, user, info) => {
try{
console.log(req.body);
if(err){
const error = new Error('An error occurred');
return next(error);
}
const message = info.message;
if(!user){
res.status(401).json({ success: false, message });
return
}
return res.json({succes: true, message});
}catch(error){
return next(error);
}
})(req, res, next);
};
I already tried adding the body parsing and nothing changed. I've searched every forum but still no answer.
I see you are saving email and password at DB, but you don't save salt to compare recieved password from user on sign in with that you have saved at DB.
https://en.wikipedia.org/wiki/Salt_(cryptography)
You steps to auth.
Get user by email from database.
Check that hashed password that you recieved from user + salt saved at database for this user equals hashed password at your database. Or just remove salt, don't hash password (but it strongly not recommended), after it will be work, try to complicate the task and hash passwords without salt. After it will be works, try to add salt.
Look at this example how to use passport with local strategy + Express.
https://github.com/jaredhanson/passport-local/wiki/Examples

Handlebars fails to access property of an object

After authentication (with passport module), a template was rendered with the req.user object as follow,
app.get('/', (req, res) => {
console.log(`Router get user: ${req.user}`);
console.log("Router get user of type: " + (typeof req.user));
res.render('index', {
layout: false,
user: req.user,
});
});
I checked the req.user by console.log, and the user object was printed as expected,
Router get user: {
_id: new ObjectId("629e3821bfb2869c42ac3c4b"),
username: 'me',
password: '123'
}
The second console.log showed the type of req.user is object,
Router get user of type: object
After convert the req.user to a string,
app.get('/', (req, res) => {
console.log(JSON.stringify(req.user));
console.log("Router get user of type: " + (typeof req.user));
res.render('index', {
layout: false,
user: req.user,
});
});
the output became,
{"_id":"629e3821bfb2869c42ac3c4b","username":"me","password":"123"}
Router get user of type: object
If I further log req.user.username as follow,
app.get('/', (req, res) => {
console.log(req.user.username);
console.log("Router get user of type: " + (typeof req.user));
res.render('index', {
layout: false,
user: req.user,
});
});
I got the error,
TypeError: Cannot read properties of undefined (reading 'username')
at /Users/Wei/github/play-js/express/authentication/src/app.js:87:24
But when I used user.username in the template file, it didn't show the username.
<body>
{{#if user}}
<h1>WELCOME BACK {{user.username}}</h1>
{{/if}}
</body>
But when I replace the {{user.username}} by {{user}}, the user object was printed correctly,
<body>
{{#if user}}
<h1>WELCOME BACK {{user}}</h1>
{{/if}}
</body>
WELCOME BACK { _id: new ObjectId("629e3821bfb2869c42ac3c4b"), username: 'me', password: '123' }
But according to the Handlebars Doc, Handlebars expression CAN be dot-separated paths.
So what's the problem here?
Here's the complete code how I set up the express server and passport authentication,
// connect to MongoDB
const mongoDB = process.env.DB_URI;
mongoose.connect(mongoDB);
const db = mongoose.connection;
db.on('error', console.error.bind(console), 'MongoDB connection error');
// Schema & Model
const userSchema = new Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
}
});
const User = mongoose.model('User', userSchema);
// Express server
const app = express();
app.set('views', path.join(__dirname, 'views'));
const eh = handlebars.create(); // ExpressHandlebars instance
app.engine('handlebars', eh.engine); // register the engine() function
app.set('view engine', 'handlebars');
// Middleware
app.use(morgan('dev')); // logger
app.use(session({
secret: 'cats',
resave: false,
saveUninitialized: true,
}));
app.use(passport.initialize());
app.use(passport.session()); // this middleware will set cookie in client computer for each session.
app.use(express.urlencoded({
extended: false,
}));
// Verify username & password in our database
// Register the LocalStrategy to the passport.
passport.use(
new LocalStrategy(function verify(username, password, done) {
User.findOne({username: username}, (err, user) => {
if (err) return done(err);
if (!user) return done(null, false, {message: 'Incorrect username'});
if (user.password !== password) return done(null, false, {message: 'Incorrect password'});
return done(null, user);
});
})
);
// Only store user._id in the cookie.
passport.serializeUser(function(user, done) {
console.log(`serialize: ${user._id}`);
done(null, user._id);
});
// Get the user object from database by searching user._id.
passport.deserializeUser(function(_id, done) {
console.log(`deserialize search for: ${_id}`);
User.findById(_id, function(err, user) {
console.log(`deserialization find user: ${user}`);
done(err, user);
});
});
// router
app.get('/', (req, res) => {
console.log(JSON.stringify(req.user));
console.log("Router get user of type: " + (typeof req.user));
res.render('index', {
layout: false,
user: req.user,
});
});
app.post('/log-in', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/',
}));
With the help of #Phil, we found the bug in deserializeUser() function.
// Get the user object from database by searching user._id.
passport.deserializeUser(function(_id, done) {
User.findById(_id, function(err, user) {
console.log(`deserialization find user: ${user}`);
done(err, user);
});
});
passport.deserializeUser() simply extracts user._id stored in cookies, and search for entire user object from MongoDB, which will be lately set as our req.user.
The problem is that User.findById() return a mongoose Document object, which doesn't have its own username property.
What we need here is a plain javascript object. We can achieve that by enable the lean option right after the findById() function.
// Get the user object from database by searching user._id.
passport.deserializeUser(function(_id, done) {
User.findById(_id, function(err, user) {
console.log(`deserialization find user: ${user}`);
done(err, user);
}).lean(); // it returns a plain javascript object now
});

Passport JS, values on error redirect are blank

I'm using the standard passport local strategy (with express.js) for the signup form on my website. When the failureRedirect is invoked, it redirects back to my signup form correctly, but all the values of my form are wiped blank. I get why this is happening, because of the redirect, but ... This is incredibly annoying for the user if they've made a simple mistake like forgetting a checkbox or their username is already taken. (Also, I know the password should not be sent back to the view). Is there a way to persist the users entered values even after the redirect with passport?
//my route
.post('', passport.authenticate('local-signup', {
failureRedirect: '/account/signup', // redirect back to the signup page if there is an error
failureFlash: true // allow flash messages
}), function(req, res) {
...
});
passport code
passport.use('local-signup', new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
}, function(req, username, password, done) {
process.nextTick(function() {
if(password != params.password2) {
return done(null, false, req.flash('error', 'Passwords do not match.'));
}
User.findOne({
'username': username
}, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
if (user) {
return done(null, false, req.flash('error', 'That username is already taken.'));
} else {
... create a new user ...
}
});
});
}));
function(req, username, password, done) {
What if you wrap passport in your route logic. For example,
app.post('/login', function(req, res, next) {
// Do something here with the username & password, like
// checking if the username is available.
if (!username || !password) {
// Render the login form, and pass in the username and password.
res.render('/login', {username: username, password: password});
} else {
// If all is validated, attempt the login:
passport.authenticate('local-signup', {
failureRedirect: '/account/signup',
failureFlash: true
}), function(req, res) {
...handle response here...
}
}
});
I'm not sure if all of that is syntatically correct, but the idea is to do whatever application-specific validation you have to do before you attempt to authenticate with Passport.

User not serialized when using async auth route and passport.js

I've got a node app that operates in isolation. Without js on in the client it just operates synchronously, setting cookies with passport.js. When the client is js enabled then the auth is done via a rest route.
All seems fine, except that if I have authenticated (and have cookies set) asynchronously, but then refresh the page (or navigate synchronously to a new one [for whatever reason]), the server sends the response with new cookies that overwrite the old ones and setting the user back to being unauthenticated.
// =============================================================================
// AUTHENTICATE (FIRST LOGIN) ==================================================
// =============================================================================
// process the login form
app.post('/login', passport.authenticate('local-login', {
successRedirect: '/profile', // redirect to the secure profile section
failureRedirect: '/browse?p=0', // redirect back to the signup page if there is an error
failureFlash: true // allow flash messages
}));
app.post('/async/login', function(req, res, next) {
passport.authenticate('local-login', function(err, user, info, status) {
if (err) {
return res.send(err);
}
if (user) {
user.local = null;
return res.send(user);
} else {
return res.send(info);
}
})(req, res, next);
});
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true // allows us to pass in the req from our route (lets us check if a user is logged in or not)
},
function(req, email, password, done) {
// asynchronous
process.nextTick(function() {
User.findOne({
'local.email': email
}).populate({
path: 'spots comments currentLocation'
}).exec(function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// if no user is found, return the message
if (!user)
return done(null, false, req.flash('loginMessage', 'No user found.'));
if (!user.validPassword(password))
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.'));
// all is well, return user
else
User.populate(user, {
path: 'spots.bird',
model: 'Bird'
}, function(err, user) {
if (err)
return done(err);
else
User.populate(user, {
path: 'spots.location',
model: 'Location'
}, function(err, user) {
if (err)
return done(err);
else
console.log(util.inspect(user, showHidden = false, depth = 5, colorize = true));
return done(null, user);
});
});
});
});
}));
Node App:
app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(cookieParser());
sessionVars = {
keys: ['<removed>'],
//secureProxy: true, // if you do SSL outside of node
maxAge: 2592000000
};
app.use(session(sessionVars));
app.use(flash());
require('./../modules/auth/passport')(passport);
app.use(passport.initialize());
app.use(passport.session());
Does anyone know why?
UPDATE: The async route doesn't seem to call the serializeUser function (which explains everything!). Does anyone know how to force serialization?
If I try a direct call: passport.serializeUser(user, done) then I need the done parameter, which isn't available in the routes file (although I could pass it through);
Does anybody know if serializeUser is meant to be called as a matter of course with passport strategies?
So I found that if you use the custom form of the authenticate function:
app.get('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
it doesn't seem to pass it through the serializeUser function that you provide. The cookies for sessions stay in your browser and change all the time, even when you're logged out as they probably still 'tracking' your behaviour.
If you use the 'black box' version of the function:
app.post('/login',
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login' }));
that works because it calls serializeUser automatically.

Passport.js LocalStrategy logic

I want to understand how does LocalStrategy work.
Here is a part of my server file:
var passport = require('passport');
var express = require('express');
/* other initializations */
var app = express();
passport.use = new LocalStrategy(
function(email, password, done) {
module.exports.findByUsername(email, function(err, user){
if (err) throw err;
if(!user) {
done(null, false, { message: 'Incorrect username.' });
}
else if(user.password != password) {
done(null, false, { message: 'Incorrect password.' });
}
else {
return done(null, user);
}
});
}
)
app.post("/login"
, passport.authenticate('local',{
successRedirect : "/",
failureRedirect : "/login",
}) ,
function(){
console.log("post /login");
}
);
Now, from a client browser, I'm sending a http post request to http://localhost:8000/login . If authentication is success then user will be redirected to the root page "/" and if failure, user will be redirected to login page again.
The question is, when we are defining a new LocalStrategy, I define a function(email,password, done){...}. However, when I'm calling this function at app.post("/login", ...){...} how do I pass the email and password parameters?
passport assumes by default that you POST a form with input name='username' input name='password'. override it as described in passport docs:
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
},
function(email, password, done) {
// ...
}
));

Categories

Resources