I am building a SPA using express, mongoose and passportjs.
I have created a simple schema for my user:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var passportLocalMongoose = require('passport-local-mongoose');
var User = new Schema({
username: String,
password: String,
first_name: String
}, { collection: 'users' });
User.plugin(passportLocalMongoose);
mongoose.model('User', User);
Configured passport using the User object which mongoose gave me:
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
And configured my app to authenticate the user when navigating to this route:
app.post('/login', passport.authenticate('local'), function(req, res, next) {
if (req.isAuthenticated()) {
return res.json({ state: 'success', user: { first_name: req.user.first_name } });
}
return res.json({ state: 'failure', message: 'cannot authenticate' });
});
Now I am able to successfully authenticate a user. And the browser saves a session id cookie.
My problem is that every time the user refreshes the page passport won't deserialize the user using the session id, what makes the user unauthenticated.
While accessing the req.user object in the site's root I get undefined what make me realize passport doesn't deserialize the user properly:
app.use('/', function (req, res, next) {
console.log(req.user); // prints undefined.
next();
});
What is the proper way for restoring a user after refreshing the page?
The solution is to store the sessions in the DB. I used connect-mongo.
app.js:
var mongoose = require('mongoose');
var expressSession = require('express-session');
var MongoStore = require('connect-mongo')(expressSession);
mongoose.connect(db_url, function (err) {
if (err) {
console.log(err);
}
});
app.use(expressSession({
secret: process.env.SESSION_SECRET || 'keyboard cat',
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
after this, req.isAuthenticated() returns true on every request even after refreshing the page. (Thanks to app.use(passport.session()) which comes before any route handler)
app.use('/', function (req, res, next) {
if (req.isAuthenticated()) {
// returns true if a user already logged in.
}
next();
});
Looking at your post i can guess where problem is happening. In question you have pasted your back end expressjs code, but the problem is happening in the front end.
Think of a situation when you are working on normal javascript files, you make some edit in the variable values using Chrome dev tools of firebug n all. Let's say you refresh the page, do you still see the same edited value in the view? No right. Same is the case with your view, you are temporary holding user data. Although your backend is holding the values in req.user but your front end loses it when you refresh.
So you have to do either of below two :
Store your value in cookie on successful login and erase it when
logged out. So that cookie is never going to loose data even if you
refresh the page. Access the user data from that cookie whenever you need it
Call the backend API which returns value of req.user on your every
page refresh
I am not sure what frontend framework you are using for SPA, if you are using AngularJS then you can make use of cookieStore service it does the job beautifully.
Related
I'm a beginner with all things Node and trying to configure a simple passport-local strategy for my app. I was able to create a new user in my database, but beyond that I'm having trouble getting passport.js to work as expected.
Here's my express app structure:
routes
- api
- events.js
- users.js
services
- passport.js
server.js
In server.js I'm configuring the routes individually, like app.use('/api/users', require('./routes/api/users'). Just before that I'm doing the following passport setup:
const session = require('express-session');
const cookieParser = require('cookie-parser');
const passport = require('passport');
const flash = require('connect-flash');
require('./services/passport')(passport);
app.use(session({ secret: process.env.SESSION_SECRET,
resave: true,
saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
My passport.js file looks like this:
const knex = require('../db/knex.js');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');
const saltRounds = 12;
module.exports = function(passport) {
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function(req, email, password, done) {
knex('users')
.where({email: email}).first()
.then(function(user) {
if (user) { return done(null, false, {message: 'User with that email already exists!'}) }
else {
bcrypt.hash(password, saltRounds, function(err, hash) {
var newUser = new Object();
newUser.username = req.body.username;
newUser.email = email;
newUser.password = hash;
knex('users')
.insert(newUser)
.then(done(null, newUser))
.catch(err => { return done(err) })
})
}
})
.catch(err => { return done(err) });
}));
};
And then in users.js I have:
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const passport = require('passport');
...
router.post('/signup', passport.authenticate('local-signup', {
successRedirect: '/',
failureRedirect: '/',
failureFlash: true
}));
I'll eventually want to make successRedirect and failureRedirect different from each other, of course, but for now I just want to understand how to make them work. As I mentioned, this code successfully inserted a user into the database. But when I try to create another account with the same credentials, nothing happens. The behavior I'd expect is to see the message "User with that email already exists!" and be redirected to my homepage.
Note that my homepage is at http://localhost:3000, so I'm not sure if setting the redirect to '/' points there or to the current page. The current page is http://localhost:3000/auth, which contains a React component with my signup form, making a POST call to /api/users/signup/. So it's conceivable that '/' would actually look for http://localhost:3000/api/users/signup which as of now doesn't have a GET method. But I'm not getting a 404 or anything; nothing happens, and there's no error. Any advice greatly appreciated.
UPDATE
I just confirmed that removing both redirects causes a 401 response as expected. Also, setting the failureRedirect to /events produces a 404, which is odd because http://localhost:3000/events is definitely configured.
EDIT
I realized it might help to show how I'm making the API call on the client side. Pressing a submit button calls the following signup function:
signup(e) {
e.preventDefault();
axios.post('/api/users/signup', {
username: this.state.username,
email: this.state.email,
password: this.state.password
})
.catch(err => console.log(err));
}
Is your express backend just used as a data API? Because if it is then I'm not sure if the PassportJS redirection would work.
I remember using it in a completely server side rendered app, where the routing was completely on backend, that kind of situation makes it work because any routing is handled on backend and it renders the page then sends it to client.
On a react app, the routing (the actual change in page/url) is handled on frontend, so doing it in Passport like this won't work. I think the best you can do is have error handling in passport that sends some kind of status or message to frontend and then handle it on frontend because that's where the routing is.
For those still facing an issue with sessions resetting when using Passport, I spent hours tracing and debugging only to find out that Passport's authenticate() resets the entire request session internally without so much as a warning. The documentation is just awful, but anyway...
The culprit is the SessionManager part of the framework, as can be seen here
// this line below
req.session.regenerate(function(err) { ... });
Passing the option keepSessionInfo: true to passport.authenticate will preserve the session, i.e:
passport.authenticate('my-strategy', {
... ,
keepSessionInfo: true
})(req, res, next);
I am using react with express.js (inside router/user.js)
router.get("/", function (req, res) {
console.log("this is u" , req.user)
console.log(req.isAuthenticated());
if (req.user) {
res.json({ user: req.user })
} else {
res.json({ user: 'does not exsist' })
}
});
Here the console.log show the value against them as always undefined
console.log("this is u" , req.user)
console.log(req.isAuthenticated());
The above code always console.log false, Now I went through the other examples where they mentioned the problem can be due to the way you put things in server.js(or app.js) and hence I checked and think that my problem is not because of that reason, Anyway this is how I am adding stuff
app.use(session({
secret: 'keyboard cat', // -> used to encode and decode the session, the seceret we used will be used to encode to decode
resave: true,
saveUninitialized: true,
}));
app.use(passport.initialize());
app.use(passport.session());
//Session
passport.use(new LocalStrategy(user.authenticate()));
passport.serializeUser(user.serializeUser());
passport.deserializeUser(user.deserializeUser());
Here is my complete proper repository if someone wants to view: https://github.com/irohitb/litifier-
Now, Can anyone please guide me about fixing this problem?
This is because the login process is not being executed.
This results into cookie not being sent, which results into cookie not being read in the next request, which results into req.isAuthenticated() returning false
From the passport.js docs,
Note that when using a custom callback, it becomes the application's
responsibility to establish a session (by calling req.login()) and
send a response.
Solution:
As this looks like your signup page, I dont think you need to do passport.authenticate here. That is for login. Remove that.
After you register the new user, do the following:
req.logIn(user, function (err) {
if (err) {
return next(err);
}
return res.json(user) // send whatever you want or redirect
});
Try adding {credentials: 'include'} in the route
Example
get('/test', {credentials: 'include'})
I am building out a RESTful app using node.js, mongoose, express & passport using .ejs as a front end. I'm trying to add authentication back in with passport. With the current code I can register a new user, but that new user does not remain logged in, and after creating the user, I cannot login to do activities requiring authorization. There is no persistent user it seems. I'm using express-session as well.
I've searched for similar issues but any "fixes" have not seemed to help. I have nearly identical code in a similar app with the same stack...the main difference being the names of the resources (ie races instead of projects for example)
This will be the 3rd application I've used passport with, so it's not 100% new and with similar / same code, it has worked on my local installation. in this case, it was working fine including showing and hiding the login/logout buttons in my .ejs file.
Then I refactored the routes and now I cannot get passport to work at all.
I have compared code, placed the auth routes back into the app.js file, tried to console.log the req.user with and without logging in and it just won't work anymore...none of the passport routes seem to function.
I've reinstalled the node modules from my package.json file, copied & pasted the passport setup from previously working files, and the req.user is always undefined, and I'm unable to register a new user.
I've essentially reinstalled all of the passport stuff, short of deleting the auth file and app.js lines and restarting...but I should be able to trouble shoot this without deleting content.
Sadly I did not save a version prior to refactoring. :(
Any suggestions on why this may have occurred AFTER I refactored the routes files when it was working well just prior? As far as I can tell I have reconstructed things as they were prior to refactoring.
Here is my app.js passport setup
//setup passport
app.use(require("express-session")({
secret: "Fig is the best puppy ever",
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
app.use(function(req, res, next){
console.log("Current User: " + req.user)
res.locals.currentUser = req.user;
next();
});
and my auth routes:
//Authentication Routes
//show signup form
app.get('/register', function(req,res){
res.render("register")
})
//user signup
app.post("/register", function(req,res){
//res.send("Signing you up")
console.log(req.body)
var newUser = new User({username: req.body.username})
User.register(newUser, req.body.password, function(err, user){
if(err) {
console.log(err);
return res.render('register')
} else {
passport.authenticate("local")(req,res, function(){
console.log("Created new user")
res.redirect("/projects")
})
}
})
})
//LOGIN ROUTES
//render login form
app.get("/login", function(req,res){
res.render("login")
})
app.post("/login", passport.authenticate("local",
{
successRedirect: "/projects",
failureRedirect: "/login"
}), function(req,res) {
// console.log("Logged in" +currentUser)
// res.redirect("/projects")
})
//Logout Routes
app.get("/logout", function(req,res) {
req.logout()
res.redirect("/projects")
})
It looks like you're missing the req.login call on authenticate. Try adding this to your else in your register middleware.
passport.authenticate("local")(req, res, function() {
req.login(user, function(err) {
if (err) {
console.log(err);
res.redirect('/register');
} else {
res.redirect('/projects')
}
})
})
Solution by OP.
Solved by reordering my route requirements and the passport set up. The correct sequence is below.
//setup passport
app.use(require("express-session")({
secret: "Fig is the best puppy ever",
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
//I MOVED THESE LINES TO BELOW THE PASSPORT SETUP
// Route setup
app.use("/users", userRoutes)
app.use(authRoutes)
I have an Angular app built on the MEAN stack, using Passport to log in and authenticate users. I'm a little confused about how authentication works.
I have a route that Angular passes an $http call to to check if a user is logged in (and can thus access certain pages). The route looks like this:
// route to test if the user is logged in or not
app.get('/loggedin', function(req, res) {
res.send(req.isAuthenticated() ? req.user : '0');
});
From what I've read (which is very little, I can't find isAuthenticated() anywhere in the Passport docs...), Passport should be creating a persistent session for my users.
This works for the most-part, but if I close my Chrome application / reset my computer I have to log in again. I assumed that using Passport would mean that I don't need to create a hashed cookie to store login information. Is this not the case?
Other potential cause: I'm in development at the moment and am restarting the server often. Will the Passport sessions not persist through a server restart?
Edit: Here is my session config in app.js:
var session = require('express-session');
app.use(session({ secret: 'heregoesasupersecretsecret' })); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
If you are using passport for authentication in Node.js, you can use a middleware to check the authentication. I use the below code for this.
passport.use(new LocalStrategy({passReqToCallback : true}, function(req, username, password, done) {
models.Accounts.findOne({ where: { username: username}}).then(function(user) {
if (!user) {
console.log('Unkown User');
return done(null, false, {message: 'Unknown User'});
}
bcrypt.compare(password, user.password, function(err, isMatch) {
if (err) throw err;
if (isMatch) {
req.session.username = req.body.username;
return done(null, user);
} else {
return done(null, false, {message: 'Invalid Password'});
}
});
}).catch(function(err) {
return done(null, false);
});
}));
You can use express-session library in Node.js to handle user sessions. You can add a middleware in your code like below.
app.use(session({
secret: 'track_courier_application_secret_key',
cookie: {
maxAge: 300000
},
saveUninitialized: true,
resave: true
}));
In the above code I'm setting maxAge to 300K milli seconds. That is 5 minutes. After this a session object will be attached in every request to the server. You can access this session object using
req.session
Now you can write a get request like below to check if a user is logged in or not.
app.get('/accounts/isloggedin', function(req, res) {
if(req.session.username)
res.status(200).send('Hurray!');
else
res.status(401).send('User not logged in.');
});
You need to use a proper session store to put the sessions in to persist them between requests. Since you're using MongoDB, you could use connect-mongo.
Then you could do something like:
var session = require('express-session'),
MongoStore = require('connect-mongo')(session);
app.use(session({
secret: 'heregoesasupersecretsecret',
store: new MongoStore()
}
));
app.use(passport.initialize());
app.use(passport.session());
How can I maintain my SESSIONS in Node.js?
For example, I want to store UserID in SESSION using Node.js. How can I do that in Node.js? And can I use that Node.js SESSION in PHP too?
I want the following in Node.js:
<?php $_SESSION['user'] = $userId; ?>
First install the session package
npm install express-session --save
Initialization of the session on your server page
var express = require('express');
var session = require('express-session');
var app = express();
app.use(session({secret: 'ssshhhhh', saveUninitialized: true, resave: true}));
Store session
sess = req.session;
var user_id = 1;
sess.user_id = user_id;
Access the session
sess = req.session;
sess.user_id
Let me divide your question in two parts.
How can I maintain my SESSIONS in Node.js?
Answer: Use express-session middleware for maintaining SESSIONS
Can I use that a Node.js SESSION in PHP too?
Answer:
Yes, you can use that session in PHP too, but keep in mind you have to store that session in the database.
ExpressJS has official session middleware, and it is also the current de-facto standard web framework for Node.js.
If you wish to implement session support on your own, this is how the implementation is normally done, upon every request:
Check if the cookie contains a session ID
If not, create a session object that is either stored in memory, on file, or in a database (or a combination of those), and set the session id in the response cookie to match this object's identifier.
If the cookie does contain a session ID, locate the session object by the ID.
Provide the obtained/created object from step 1 as the persisted session object for the request.
You will also have to implement some timeout mechanism, so that after a while the session objects are deleted, at least from memory.
You could use the express-session middleware.
Combine it with connect-redis or connect-mongo to store your sessions inside a database and save memory if memory is valuable to you (like in a cloud setup).
express-sessions (npm)
If you store it in, say, MongoDB, use the PHP MongoDB driver to pick it up from there.
You don't need to do it by yourself. There are some amazing modules in Node.js that handle this kind of things for you.
You can use session middleware from Express.js, as suggested before.
However, I'd recommend you to use Passport.js. This module does the authentication part for you, has a lot of strategies that you could integrate in your website (log in with Facebook, Google, Twitter, etc.), and deals with all the session stuff automatically, using serializeUser() and deserializeUser() functions whenever you need to.
You can take a look at this here, within the "Sessions" section: Configure Passport.js
Session that gives access/permission to view a user's area, as well as it's a credential, so we can use it over the application.
I used jsonwebtoken to make a token which will has the user's details with time after a successful login attempt by the user. I stored it in Redis, and it can be used for a pre-declared time limit.
To maintain a session is now older, and you should try with using JWT token. It is very effective and easy. But still to maintain the session in Node.js:
In your Express.js configuration:
var cookieParser = require('cookie-parser');
var session = require('express-session');
app.use(cookieParser());
app.use(session({
secret: 'secret',
resave: true,
saveUninitialized: true,
rolling: true,
cookie: {
path: '/',
maxAge: 60000 * 1000
},
name: 'SID'
}));
Store session after Login:
var session = req.session;
if (user) {
session.user = user._id;
session.save();
console.log(session);
}
Check Session from middleware:
var session = req.session;
if (session.user) {
req.userid = session.user;
next();
} else {
return res.status(401).send({
code: 401,
message: Constant.authentication_fails
});
}
Follow the below steps:
npm install express-session --save
Write the below code:
var express = require('express');
var session = require('express-session');
var app = express();
app.use(session({secret: 'your secret key', saveUninitialized: true, resave: true}));
var userId = 1234;
app.get('/', function (req, res, next) {
req.session.userId = userId;
});
Storing a session in Node.js is fairly easy but you need to understands its step, you could handle this manually, also you can use few NPM modules. Passport can help you to authenticate and login and store the session i would recommend you to read its documentation, Passport allow you to authenticate user with different other platform like Google, github many more.
If you are going to use passport use these below NPM module
Passport
Passport Local
Express-flash
Express-session
2 -Import these modules in your main app.js:
const flash = require('express-flash')
const session = require('express-session')
const passport = require('passport')
app.use(session({
secret:'secret',
resave:false,
saveUninitialized:false
}))
app.use(flash())
app.use(passport.initialize())
app.use(passport.session())
3- Create the passport.js file. You can name anything. So basic understanding behind this is that you have to check the valid user coming from your input form, and you have to compare the email id with your model. If it is valid, check the password and then return the user. Once that is done, serialize and deserialize your user data to store in the session..
I would recommend to check this part in the documentation for more clear understanding: Overview
const localStrategy = require('passport-local').Strategy
const bycrypt = require('bcrypt')
const User = require('../model/User')
const initalize = function(passport) {
const auth = async(email, password, done) => {
try {
const user = await User.findOne({email:email})
if(!user) {
throw new Error("Incorrect Email ..!")
}
const match = await bycrypt.compare(password, user.password)
if(!match) {
throw new Error('Incorrect Password..!')
}
return done(null, user)
}
catch (error) {
console.log(error)
done(null,false,error)
}
}
passport.use(new localStrategy({usernameField:'email'}, auth))
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
}
module.exports = initalize
4 - Now go to your login router and use the below code
const passport = require('passport')
require('../passport/passport')(passport)
routes.get('/signin', (req,res) => {
res.render('signin', {
pageTitle: 'sign in'
})
})
routes.post('/signin', passport.authenticate('local', {
successRedirect: '/welcome',
failureRedirect: '/',
failureFlash: true
}))
You can use sessions in Node.js by using the 'express-session' package in Node.js.
You have to install express and express-session in your application:
const express = require('express');
const session = require('express-session');
const app = express();
"secret" is used for the cookie, and we have to add some secret for managing a session.
"request" we use as a request variable as we use $_SESSION in PHP.
var sess;
app.get('/',function(req,res){ // Get request from the app side
sess = req.session;
sess.email; // Equivalent to $_SESSION['email'] in PHP.
sess.username; // Equivalent to $_SESSION['username'] in PHP.
});
Here is full documentation in Code for Geek about the session in Node.js if you want to learn in detail about the session in Node.js.
You can handle the session in two ways.
Using express-session
Using JWT web token and handle your own session (token-based session handling).
I think token-based session handling is more important rather than using express-session. You will get a problem when you scale your server and also a problem with some single device login situation.
For checking I have a token-based session handling Node.js folder structure. You can check it, and it may be helpful.