Error: Failed to serialize user into session || This error is coming while I am trying to log in - express

I got a problem with the Passport.js module and Express.js.
This is my code and I just want to use a hardcoded login for the first try.
I always get the message:
I searched a lot and found some posts in stackoverflow but I didnt get the failure.
Code of my app.js
require('dotenv').config()
const express = require('express')
const bodyParser = require('body-parser')
const ejs = require('ejs')
const mongoose = require('mongoose')
const session = require('express-session')
const passport = require('passport')
const passportLocalMongoose = require('passport-local-mongoose')
const app = express();
const port = process.env.PORT
app.use(express.static('public'))
app.set('view engine','ejs')
app.use(bodyParser.urlencoded({extended:true}))
app.use(session({
secret: process.env.SECRET_KEY,
resave: false,
saveUninitialized: false,
}))
app.use(passport.initialize())
app.use(passport.session())
mongoose.connect("mongodb://localhost:27017/userDB")
const userSchema = new mongoose.Schema({
username: String,
password: String,
})
userSchema.plugin(passportLocalMongoose)
const User = new mongoose.model("User",userSchema)
passport.use(User.createStrategy())
passport.serializeUser(User.serializeUser())
passport.deserializeUser(User.deserializeUser())
//Home Route
app.get('/',(req,res)=>{
res.render('home')
})
//Login Route
app.route('/login')
.get((req,res)=>{
res.render('login')
})
.post((req,res)=>{
const user = new User({
username: req.body.username,
password: req.body.password
});
req.login(user, function(err){
if (err) {
console.log(err);
} else {
passport.authenticate("local")(req, res, function(){
res.redirect("/secrets");
});
}
});
})
//Register route
app.route('/register')
.get((req,res)=>{
res.render('register')
})
.post((req,res)=>{
User.register({username: req.body.username}, req.body.password,(err,user)=>{
if(err){
console.log(err);
res.redirect('/register')
}
else{
passport.authenticate("local")(req,res,()=>{
res.redirect("/secrets")
})
}
})
})
app.route('/secrets')
.get((req,res)=>{
res.set('Cache-Control', 'no-store');
if(req.isAuthenticated()){
res.render("secrets")
}
else{
res.redirect("/login")
}
})
app.listen(port,()=>console.log("server started at port "+port))
But its giving this error:
Error: Failed to serialize user into session
at pass (D:\CODING\WEB\Web-Development-Series\Secrets+-+Starting+Code\Secrets - Starting Code\node_modules\passport\lib\authenticator.js:278:19)
at serialized (D:\CODING\WEB\Web-Development-Series\Secrets+-+Starting+Code\Secrets - Starting Code\node_modules\passport\lib\authenticator.js:283:7)
at D:\CODING\WEB\Web-Development-Series\Secrets+-+Starting+Code\Secrets - Starting Code\node_modules\passport-local-mongoose\index.js:212:7
at pass (D:\CODING\WEB\Web-Development-Series\Secrets+-+Starting+Code\Secrets - Starting Code\node_modules\passport\lib\authenticator.js:291:9)
at Authenticator.serializeUser (D:\CODING\WEB\Web-Development-Series\Secrets+-+Starting+Code\Secrets - Starting Code\node_modules\passport\lib\authenticator.js:296:5)
at SessionManager.logIn (D:\CODING\WEB\Web-Development-Series\Secrets+-+Starting+Code\Secrets - Starting Code\node_modules\passport\lib\sessionmanager.js:14:8)
This problem is occuring only in case of login. register is working fine.

This error means passport is not able to hash (serialize) your users. Try to implement your own User.serializeUser() function using the user's _id.

Related

How to display logged in username on the screen | express session

I want take the user info like email , username and display it on dashboard.ejs . i tried req.session.username and req.body.username but never worked . please help me in this !!
i want handle profile management for the web so by retriving the username help fetch the info about the user in the database
`
const express = require("express");
const app = express();
const bcrypt = require("bcryptjs");
const session = require("express-session");
const MongoDBSession = require("connect-mongodb-session")(session);
const mongoose = require("mongoose");
const UserModel = require("./models/user");
const mongoURI = "mongodb://localhost:27017/sessions";
mongoose.connect( mongoURI, {
useNewUrlParser : true,
// useCreateIndex : true,
// useUnifiedToplogy : true
}).then((res)=>{
console.log("MongoDB connected");
})
const store = new MongoDBSession({
uri : mongoURI,
collections : "mySessions"
})
const isAuth = (req,res,next)=>{
if(req.session.isAuth){
next();
}else{
res.redirect("/login");
}
}
app.use(session({
secret : "key that will sign a cookie",
resave : false,
saveUninitialized : false,
store : store
}))
app.set("view engine", "ejs");
app.use(express.urlencoded({ extended: true }));
app.get("/", (req,res)=>{
res.render("landing");
});
// Login Page
app.get("/login", (req,res)=>{
res.render("login");
} );
app.post("/login", async(req,res)=>{
const {email , password} = req.body;
const user = await UserModel.findOne({email});
if (!user){
return res.redirect("/login");
}
const isMatch =await bcrypt.compare(password , user.password);
if(!isMatch){
return res.redirect("/login");
}
req.session.isAuth = true;
res.redirect("/dashboard");
});
// Register Page
app.get("/register", (req,res)=>{
res.render("register");
});
app.post("/register", async (req,res)=>{
const {username , email , password } = req.body;
let user = await UserModel.findOne({email});
if (user){
return res.redirect("/register");
}
const hashPsw =await bcrypt.hash(password,12);
user = new UserModel({
username,
email,
password:hashPsw
});
user.save();
console.log("saved");
if (!user){
return res.redirect("/login");
}
const isMatch =await bcrypt.compare(password , user.password);
if(!isMatch){
return res.redirect("/login");
}
req.session.isAuth = true;
res.redirect("/dashboard");
});
// Dashboard Page
app.get("/dashboard", isAuth , (req,res)=>{
console.log(req.session);
res.render("dashboard");
});
app.post("/logout", (req,res)=>{
req.session.destroy((err)=>{
if (err) throw err;
res.redirect("/");
})
});
app.listen(3500 , ()=>{
console.log("server running on port 3500");
})
`
One of the options would be to supply that in the POST request's response. For example, Author has inserted it in a pug view here like h2 Username: #{user.name} and has supplied it as variable while page rendering in response here with res.render('profile', {title: "My Profile", user: req.cookies.userData});.
Since you have used ejs instead of pug, the injecting I did with #{} can be done with <%= YOUR_VARIABLE %> . Please, see this for a tutorial and ejs docs for more examples.

Using Passport.js, cookie not persisting from Heroku Rest API backend to Netlify React frontend

So I feel like I've tried everything.
Quick workflow breakdown. I have a React App deployed on Netlify that uses a Rest API backend hosted on Heroku with a Jaws MySQL instance and a Redis session store. There is an authorization workflow that uses a google0auth2.0 strategy and passport.js as well as the Redis to store each separate session which I only started using on the production build as my understanding was that express-sessions alone would cause memory leaks. That was my first step through this rabbit hole.
After some research, I added app.set('trust proxy', 1), added app.use(helmet.hsts()) to address headers issues and I attempted to secure the cookie with the code below:
app.use(
expressSession({
...
proxy: isProdEnv,
cookie: {
secure: isProdEnv
}
When checking the network tab in chrome dev tools, I see the cookie is attached to the callback however, it is not attached to the dashboard react page that the API redirects on the successful authorization.
My API index.js is below:
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const expressSession = require('express-session');
const {
createClient
} = require("redis");
let RedisStore = require('connect-redis')(expressSession);
require('dotenv').config();
const PORT = process.env.PORT || 5050;
const isProdEnv = process.env.NODE_ENV === 'production' ? true : false;
// Knex instance for DB managment
const knex = require('knex')(require('./knexfile.js')[process.env.NODE_ENV || 'development']);
const app = express();
app.use(express.json());
app.use(helmet());
app.use(helmet.hsts());
app.use(
cors({
origin: true,
credentials: true,
}),
);
let redisClient = createClient({
url: process.env.REDIS_URL,
lazyConnect: true,
showFriendlyErrorStack: true,
legacyMode: true,
retry_strategy: (options) => {
const {
error,
total_retry_time,
attempt
} = options;
if (error ? .code === 'ECONNREFUSED' || error ? .code === 'NR_CLOSED') {
return 5000;
}
if (total_retry_time > 1000 * 15) {
return undefined;
}
if (attempt > 10) {
return undefined;
}
return Math.min(options.attempt * 1000, 5000); //in ms
},
});
if (!redisClient.isOpen) {
redisClient.connect().catch(console.error);
console.info('connected to redis at', process.env.REDIS_URL);
}
redisClient.on('error', (err) => {
console.log('ⓘ on error:', err);
});
app.set('trust proxy', 1);
// Include express-session middleware (with additional config options required
// for Passport session)
app.use(
expressSession({
store: new RedisStore({
client: redisClient
}),
secret: process.env.SESSION_SECRET,
proxy: isProdEnv,
resave: false,
saveUninitialized: true,
name: 'lostnfound',
cookie: {
secure: isProdEnv
}
}),
);
// =========== Passport Config ============
// Initialize Passport middleware
app.use(passport.initialize());
app.use(passport.session());
passport.use(
new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL,
scope: ['profile', 'email'],
},
(_accessToken, _refreshToken, profile, done) => {
const id = String(profile.id);
const profileId = Number(id.slice(-18));
// First let's check if we already have this user in our DB
knex('users')
.select('id')
.where({
google_id: profileId
})
.then((user) => {
if (user.length) {
// If user is found, pass the user object to serialize function
done(null, user[0]);
} else {
// If user isn't found, we create a record
knex('users')
.insert({
google_id: profileId,
avatar_url: profile._json.picture,
first_name: profile.name.givenName,
last_name: profile.name.familyName,
email: profile._json.email,
})
.then((userId) => {
// Pass the user object to serialize function
done(null, {
id: userId[0]
});
})
.catch((err) => {
console.log('Error creating a user', err);
});
}
})
.catch((err) => {
console.log('Error fetching a user', err);
});
},
),
);
// `serializeUser` determines which data of the auth user object should be stored in the session
// The data comes from `done` function of the strategy
// The result of the method is attached to the session as `req.session.passport.user = 12345`
passport.serializeUser((user, done) => {
console.log('serializeUser (user object):', user);
// Store only the user id in session
done(null, user.id);
});
// `deserializeUser` receives a value sent from `serializeUser` `done` function
// We can then retrieve full user information from our database using the userId
passport.deserializeUser((userId, done) => {
console.log('deserializeUser (user id):', userId);
// Query user information from the database for currently authenticated user
knex('users')
.where({
id: userId
})
.then((user) => {
// Remember that knex will return an array of records, so we need to get a single record from it
console.log('req.user:', user[0]);
// The full user object will be attached to request object as `req.user`
done(null, user[0]);
})
.catch((err) => {
console.log('Error finding user', err);
});
});
// Additional information on serializeUser and deserializeUser:
// https://stackoverflow.com/questions/27637609/understanding-passport-serialize-deserialize
// Import all route types for server functionality
const authRoutes = require('./routes/auth');
const postRoutes = require('./routes/post');
app.use('/auth', authRoutes);
app.use('/post', postRoutes);
app.listen(PORT, () => {
console.log(`🚀 Server listening on port ${PORT}.`);
});
And this is my google endpoint and google callback:
const express = require('express');
const router = express.Router();
const passport = require('passport');
require('dotenv').config();
const AuthController = require('../controller/auth');
// Create a login endpoint which kickstarts the auth process and takes user to a consent page
router.get('/google', passport.authenticate('google'));
// This is the endpoint that Google will redirect to after user responds on consent page
router.get(
'/google/callback',
passport.authenticate('google', {
failureRedirect: `${process.env.CLIENT_URL}/auth-fail`,
}),
(_req, res) => {
// Successful authentication, redirect to client-side application
res.redirect(`${process.env.CLIENT_URL}/dashboard`);
},
);
I will add, I'm on the free Heroku account and haven't set up any SSL. Is that what is holding me back? is there any third-party free SSL that I can work into the workflow?
It's hosted here if you want to get a first-hand look. Click on the login to google to go through the workflow and checkout the network tab after you're authorized (if you dare, lol)

Passport user object in request not available in getInitialProps when server is on a different port

I have my express server on a different port than my client-side nextjs project.
I know when you have a server on the same port you can use getRequestHandler with next that passes the user object to be accessible with getInitialProps in the client-side.
const express = require("express");
const next = require("next");
const app = next({ dev: true });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
// adds passport session
require("./middlewares").init(server);
const apolloServer = require("./graphql").createApolloServer();
apolloServer.applyMiddleware({ app: server });
server.all("*", (req, res) => {
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
});
});
My passport implementation is as follows
const config = require("../config");
const session = require("express-session");
const passport = require("passport");
exports.init = (server, db) => {
require("./passport").init(passport);
const sess = {
name: "pid",
secret: config.SESSION_SECRET,
cookie: { maxAge: 2 * 60 * 60 * 1000 },
resave: false,
saveUninitialized: false,
store: db.initSessionStore(),
};
if (process.env.NODE_ENV === "production") {
server.set("trust proxy", 1);
sess.cookie.secure = true;
sess.cookie.httpOnly = true;
sess.cookie.sameSite = 'none';
sess.cookie.domain = process.env.DOMAIN;
}
server.use(session(sess));
server.use(passport.initialize());
server.use(passport.session());
};
And running the following on the express server, I can see req.user returning the user object.
app.use((req, res, next) => {
console.log(req.user);
next();
});
In a page in my nextjs app, in getInitialProps req.user is undefined
Home.getInitialProps = async (ctx) => {
const { req } = ctx;
const { user } = req;
console.log(user);
..........
};
Is there a way to either access the passport user object via SSR in nextjs or a different method to authorize and user on a page?
I do have a Github Repo with instructions on how to run the app in the README.md
Passport auth doesn't seems work across port. The solution is put a ngnix in front.
Local passport authorization on different ports

req.isAuthenticated() returns FALSE (express-session, passport-local)

Edit: I got the cookie to be set by transferring app.use(session({...})) from register.js to app.js before I declare use of the router in app.js. However, the problem - req.isAuthenticated() returning false, still persists.
I'm trying to build a sign up flow using passport-local, express-session and mongodb for my backend.
When I register a new user, it seems to work fine and the user gets created in my database as desired. I can also access req.user in my passport.authenticate() callback post registration. However, when I redirect to a new route using res.redirect(), req.isAuthenticated() returns false on my new route.
Upon checking the cookies in my browser, it seems that express-session never saves the session as it is supposed to. There is no cookie from my localhost. I've done hours of research, and most answers I've come across just relate to double-checking the sequence of code when initializing. I've done this countless times, as well as tried to use both passport.serializeUser(User.serializeUser()); and passport.serializeUser(function(user, done) { done(null, user.id); }); to make sure I'm doing that step correctly. I can't seem to figure out why I'm having this issue. Any and all help will be deeply appreciated! Relevant snippets from my code -
Register.js (Router)
var express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const passportLocalMongoose = require('passport-local-mongoose');
var router = express.Router();
const app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(session({
secret:"Edit Later.",
resave:true,
saveUninitialized:true
}));
app.use(passport.initialize());
app.use(passport.session());
const userSchema = new mongoose.Schema({
name:String,
username:String,
contactNumber:String
});
userSchema.plugin(passportLocalMongoose);
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
router.post('/', function(req, res, next) {
const reCaptchaKey = "<MYKEY>";
var userCaptchaToken = req.body['g-recaptcha-response'];
axios.post('https://www.google.com/recaptcha/api/siteverify', null, {params: {
secret: reCaptchaKey,
response: userCaptchaToken
}}).then(response => {
if (response.data.success === true) {
User.register({username: req.body.username, name: req.body.fullName}, req.body.password,
function(err, user) {
if (err) {
console.log(err);
// res.redirect('/views/register.html');
} else {
passport.authenticate('local', {session:true})(req, res, function() {
console.log(req.user);
console.log(req.session);
res.redirect('/profile');
console.log("Done!");
});
}
});
} else if (response.data.success === false) {
console.log("Captcha failed.");
}
}).catch(error => {
console.log(error);
});
});
module.exports = router;
Profile.js (Router)
const express = require('express');
const router = express.Router();
router.get('/', function(req, res) {
if (req.isAuthenticated()) {
res.render('profile')
} else {
res.redirect('/register')
console.log('Not a user.')
}
})
module.exports = router;
App.js
var registrationRouter = require('./routes/register');
var profileRouter = require('./routes/profile');
app.use('/register', registrationRouter);
app.use('/profile', profileRouter);
module.exports = app;
So after hours of trying, I figured it out. My issue was I used the following code in Register.js
app.use(session({
secret:"Edit Later.",
resave:true,
saveUninitialized:true
}));
app.use(passport.initialize());
app.use(passport.session());
This was a problem because Register.js was being used as a router, and I directed all requests to this route via App.js. This led to the app not being configured properly before the requests were handled by Register.js. The above lines are configuration code and MUST be written before app.use('/register', registrationRouter); in App.js. Thus, the fix was simply to move these lines over to App.js like so -
\\configure App
app.use(session({
secret:"Edit Later.",
resave:true,
saveUninitialized:true
}));
app.use(passport.initialize());
app.use(passport.session());
\\handle router
app.use('/register', registrationRouter);

Can't set a cookie w/ Nuxt.js, Express-Session

I'm new to NUXT and SSR and I've been researching this for a few hours now and I can't seem to figure it out. I'm using JWT to authenticate users in my Nuxt app with a Bearer Token, which is working great until I hit refresh and lose my session.
Now I'm looking to persist sessions using express-session and connect-mongo. I can't get the cookie to set on the client to be included on future requests.
When a user is authenticated:
router.post('/login', function(req, res) {
User.findOne({
username: req.body.username
}, function(err, user) {
if (err) throw err;
if (!user) {
res.status(401).send({success: false, msg: 'Authentication failed. User not found.'});
} else {
// check if password matches
user.comparePassword(req.body.password, function (err, isMatch) {
if (isMatch && !err) {
// if user is found and password is right create a token
var token = jwt.sign(user.toJSON(), config.secret, { expiresIn: 604800 });
req.session.authUser = { 'user': 'Test User' }
return res.json({success: true, token: token, user: user});
} else {
res.status(401).send({success: false, msg: 'Authentication failed. Wrong password.'});
}
});
}
The console.log above shows the authUser in the session.
Session {
cookie:
{ path: '/',
_expires: 2018-04-03T18:13:53.209Z,
originalMaxAge: 60000,
httpOnly: true },
authUser: { user: 'Test User' } }
When I look at my chrome devtools application cookies a connect.ssid hasn't been set and when I console.log(req.session) on future requests the authUser is missing.
My server code is:
// Passport
var passport = require('passport');
var passportJWT = require("passport-jwt");
var ExtractJwt = passportJWT.ExtractJwt;
var JwtStrategy = passportJWT.Strategy;
// Config File
let config = require('./config/settings.js')
// Initialize Express
var app = express();
// CORS-ENABLE
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://127.0.0.1:1337");
res.header("Access-Control-Allow-Credentials", 'true');
next();
});
app.use(cors())
const dbPath = 'mongodb://blogUser:blogUserPassword#localhost:27017/blog'
// Express Session
app.use(session({
secret: 'super-secret-key',
resave: false,
saveUninitialized: false,
store: new MongoStore({ url: dbPath }),
cookie: { maxAge: 60000 }
}))
// File Upload
app.use(fileUpload());
// view engine setup
// app.set('views', path.join(__dirname, 'views'));
// app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Routes
var index = require('./routes/index');
var users = require('./routes/users');
app.use('/api', index);
app.use('/users', users);
// Passport Config
app.use(passport.initialize());
app.use(passport.session())
// mongoose
const options = {
autoIndex: true, // Don't build indexes
reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
reconnectInterval: 500, // Reconnect every 500ms
poolSize: 10, // Maintain up to 10 socket connections
// If not connected, return errors immediately rather than waiting for reconnect
bufferMaxEntries: 0
};
console.log(options);
// Localhost Connect
mongoose.connect(dbPath, options).then(
() => { console.log("connected !!!"); },
err => { console.log(err); }
);
Any and all help is appreciated.
If you want to use the server you create the problem with the session is the express router, because change res and req vars so like recommend in nuxt use this.
const express = require('express');
// Create express router
const router = express.Router()
// Transform req & res to have the same API as express
const app = express()
router.use((req, res, next) => {
Object.setPrototypeOf(req, app.request)
Object.setPrototypeOf(res, app.response)
req.res = res
res.req = req
next()
})
You are missing this step
// Create express router
const router = express.Router()
// Transform req & res to have the same API as express
// So we can use res.status() & res.json()
router.use((req, res, next) => {
Object.setPrototypeOf(req, app.request)
Object.setPrototypeOf(res, app.response)
req.res = res
res.req = req
next()
})
The req, res parameters need to be interchanged on the client side
Once you do router.post('/login') and logout
app.use('/api', router)
And that will work perfectly