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)
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 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());
I'm currently encountering the following problem during implementation of passport js with the passport local mongoose plugin. Account creation and logging in is working correctly. However, after I have logged in passport never identifies me as a user that is logged in.
I have used the following pieces of code:
In my user model:
User.plugin(passportLocalMongoose);
In app.js (this order of inclusion is correct?):
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser('keyboard cat'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
In my routes:
router.post('/login', passport.authenticate('local'), function(req, res) {
res.json({ loggedIn: true });
});
which returns true, but the following keeps returning false (after logging in):
req.isAuthenticated()
Can anyone enlighten me what the cause may be?
Thanks!
You probably want to try an Express middelware, as was suggested in the comments.
For example:
function isAuthenticated(req, res, next) {
if(req.isAuthenticated()) {
return next()
} else {
// redirect users to login page
}
}
app.get('/anypage', isAuthenticated, function(req, res) {
// some reoute logic
})
From the looks of it, your order is fine. The most important part of the order is to have passport.initialize() and passport.session() come after your express-session configuration.
As for the issue with the initial authentication working, but subsequent requests showing an unauthenticated user, the issue could very well be because of cookies. I have run into a similar issue before, and the problem was in the way the HTTP requests were being made from the client.
If you are using the ES6 fetch API, then you will want to make sure to pass in an key credentials to the options object with a value of "include".
For example:
fetch('/restricted', {
method: 'get',
credentials: 'include'
});
The fetch API will not send credentials in cookies unless you specify it to. Hope this helps.
Additional resources: https://developers.google.com/web/updates/2015/03/introduction-to-fetch
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.