I setup passport on nodejs and have it working with mongoose for allowing users to login and create new accounts.
app.js:
var express = require('express')
, app = module.exports = express.createServer()
, passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, routes = require('./routes/index')(app) //index loads in multiple routes
, MongoDBConnection = require('./database/DatabaseConnector').MongoDBConnection;
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.session({ secret: 'justdoit' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
var mongoDbConnection = new MongoDBConnection();
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
mongoDbConnection.findUserById(id, function(err, user){
done(err, user);
});
});
passport.use(new LocalStrategy(
function(username, password, done) {
process.nextTick(function () {
mongoDbConnection.findUser(username, function(err, user) {
//conditions....
});
});
}
));
app.get('/', function(req, res){
res.render('index', { title: "Index", user: req.user });
});
app.get('/account', ensureAuthenticated, function(req, res){
res.render('account', { title: "Account", user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { title: "Login", user: req.user, message: req.flash('error') });
});
app.post('/login',
passport.authenticate('local', {
successRedirect: '/account',
failureRedirect: '/login',
failureFlash: true })
);
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login')
}
My problem is the app.js (which is where the passport code is) file is getting a bit large and I tried to move the passport sections into its own script and have the routes outside the app.js and in its own auth.js route file and then reference the routes via the app.js. It works for other routes but for passport related ones such as login it doesnt appear to fire the passport.authenicate() function.
Is there anyway I can put passport routes and functions into its own file and call it/load it from app.js?
auth.js:
module.exports = function(app){
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
mongoDbConnection.findUserById(id, function(err, user){
done(err, user);
});
});
passport.use(new LocalStrategy(
function(username, password, done) {
process.nextTick(function () {
mongoDbConnection.findUser(username, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: 'Unknown user ' + username });
}
if (user.password != password) {
return done(null, false, { message: 'Invalid password' });
}
return done(null, user);
});
});
}
));
app.get('/', function(req, res){
res.render('index', { title: "Index", user: req.user });
});
app.get('/account', ensureAuthenticated, function(req, res){
console.log("directing to the account page....");
res.render('account', { title: "Account", user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { title: "Login", user: req.user, message: req.flash('error') });
});
app.post('/login',
passport.authenticate('local', {
successRedirect: '/account',
failureRedirect: '/login',
failureFlash: true })
);
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login')
}
}
This is what I do. Please comment if you need more help tailoring it to your code.
First Step
Put your passport code in a separate file. e.g. pass.js. (I see you have already done that) Then, in that file, put all the code inside this:
module.exports = function(passport, LocalStrategy){
};
Remember to add to the function input anything else that you are using. In your case, besides passport and LocalStrategy, you will probably need to add mongoDbConnection as an input too.
Second Step
In your app.js, include this line. Just before "app.listen" if possible to ensure that everything has been properly defined/declared/included.
require('./pass.js')(passport, LocalStrategy);
Explanation
The "wrapper" in step one defines the chunk of code you will be including into your app. The "require" in step two is the code that actually includes it. You are basically defining the entire "pass.js" file as a function and passing it the tools it needs to carry out the code (passport, LocalStrategy etc)
In your case, you will probably need to modify my code to:
module.exports = function(passport, LocalStrategy, mongoDbConnection){
};
require('./pass.js')(passport, LocalStrategy, mongoDbConnection);
This should works. I googled about this a while ago and this appears to be the "correct" way to break up your app.js (I say this with great trepidation though :) ). Feel free to comment if you need anymore help.
This github repo also has a good example of this.
https://github.com/madhums/nodejs-express-mongoose-demo
The server.js file would be your app.js. And the /config/passport.js is the included passport setup.
For this I'll suggest to do this In app.js
require('./mypassport')(app);
And
mypassport.js
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, MongoDBConnection = require('./database/DatabaseConnector').MongoDBConnection;
module.exports = function(app){
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
mongoDbConnection.findUserById(id, function(err, user){
done(err, user);
});
});
passport.use(new LocalStrategy(
function(username, password, done) {
process.nextTick(function () {
mongoDbConnection.findUser(username, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: 'Unknown user ' + username });
}
if (user.password != password) {
return done(null, false, { message: 'Invalid password' });
}
return done(null, user);
});
});
}
));
}
Adding on to Legendre's answer. module.exports = function() is a way in nodejs to make a file, a variable or a certain functionality globally available to the entire application.
// anyfile.js
module.exports = function(){
//global code.
}
module.exports = function(app){
passport.serializeUser(function(user, done) {
done(null, user.id);
});
Maybe it doesnt work because u do not have a reference to a passport object ?
Related
I'm a hobbyist coder, and I can usually solve errors with lots of searches, but this one I can't get it right.
when I hit my logout route it throws an error: Cast to ObjectId failed for value "logout" at path "_id" for model "Spot"
I tried mongoose version 4.7.2, it's not working. I can't imagine why is it associating my logout route with the spot model at all.
my app.js
var express = require("express"),
bodyParser = require("body-parser"),
mongoose = require("mongoose"),
passport = require("passport"),
passportFacebook = require("passport-facebook").Strategy,
User = require("./models/user.js"),
Spot = require("./models/spot.js");
mongoose.connect("mongodb://localhost/biketrialspots", { useNewUrlParser: true });
var app = express();
app.set("view engine","ejs");
app.use(bodyParser.urlencoded({extended:true}));
app.use(express.static('public'));
app.use(require("express-session")({
secret: "some secret",
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(function(req, res, next){
res.locals.currentUser = req.user;
next();
});
passport.use(new passportFacebook({
clientID: "some id",
clientSecret: "some secret",
callbackURL: "somewebsite/auth/facebook/callback",
profileFields: ['id', 'displayName', 'picture.type(large)']
}, function(accessToken, refreshToken, profile, done) {
User.findOrCreate(profile, function(err, user) {
if (err)
{ return done(err); }
done(null, user);
});
}
));
passport.serializeUser(function(user, done) {
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
app.get("/", function(req, res){
Spot.find({}, function(err, spots){
if(err){
console.log(err);
} else{
res.render("index", {spots:spots});
}
});
});
app.get("/new", ensureAuthenticated, function(req, res){
res.render("new");
});
app.post("/", function(req, res){
Spot.create(req.body.spot, function(err, result){
if(err){
console.log(err);
} else{
res.redirect("/");
}
});
});
app.get("/:id", function(req, res){
Spot.findById(req.params.id, function(err, spot){
if(err){
console.log(err);
} else{
res.render("spot", {spot: spot});
}
});
});
// Redirect the user to Facebook for authentication. When complete,
// Facebook will redirect the user back to the application at
// /auth/facebook/callback
app.get('/auth/facebook', passport.authenticate('facebook'));
// Facebook will redirect the user to this URL after approval. Finish the
// authentication process by attempting to obtain an access token. If
// access was granted, the user will be logged in. Otherwise,
// authentication has failed.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { successRedirect: '/',
failureRedirect: '/login' }));
app.get("/logout", function(req, res){
req.user.logout();
res.redirect('/');
});
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
return res.redirect('/');
}
app.listen(process.env.PORT, process.env.IP, function(req, res){
console.log("APP HAS STARTED!!!!!");
});
user model
var mongoose = require("mongoose");
var userSchema = new mongoose.Schema({
facebookID:Number,
username:String,
photo:String
});
userSchema.statics.findOrCreate = function findOrCreate(profile, cb){
var userObj = new this();
this.findOne({facebookID : profile.id},function(err,result){
if(!result){
userObj.facebookID = profile.id;
userObj.username = profile.displayName;
userObj.photo = profile.photos[0].value;
userObj.save(cb);
} else{
cb(err,result);
}
});
};
module.exports = mongoose.model("User", userSchema);
Thank you
Because app.get("/:id", ...) is written before app.get("/logout", ...) in your code, I guess the request handler of /:id would be called when you get /logout. Then, req.params.id becomes "logout" and the error is thrown by Spot.findById().
How about trying to write app.get("/logout", ...) before app.get("/:id", ...)?
I am trying to use passport authentication with a local strategy but the authentication fails every time and doesn't move on to the local strategy.
I have added a couple console logs to see where the code is derailed but nothing at all is logged.
users.js(router)
var express = require('express');
var router = express.Router();
var User = require('../models/user');
var multer=require('multer');
var passport=require('passport');
var LocalStrategy=require('passport-local').Strategy;
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
router.get('/register', function(req, res, next) {
res.render('register',{
'title':'Register'
});
});
router.get('/login', function(req, res, next) {
res.render('login',{
'title':'Login'
});
});
passport.use(new LocalStrategy(
function(username,password,done){
console.log('words');
User.getUserByUsername(username,function(err,user){
if (err) throw err;
if(!user){
console.log('Unknown user');
return done(null,false);
}
});
}
));
router.post('/login',passport.authenticate('local',{failureRedirect:'/users/register'}),function(req,res){
console.log('Authentication succesful');
req.flash('success','You are logged in');
res.redirect('/');
});
module.exports = router;
Did you set the session and added passport to the router? I don't know if setting the initialize and session method in two different spots will work.
This is how I made it work: All set in in a router
let express = require('express');
let session = require('express-session');
let passport = require('passport');
let LocalStrategy = require('passport-local').Strategy;
let router = express.Router();
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({username: username}, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
user.comparePassword(password, function (err, isMatch) {
if (err) { return done(err); }
if(!isMatch){
return done(null, false, { message: 'Incorrect password.' });
} else {
return done(null, user);
}
});
});
}
));
passport.serializeUser(function(user, done) {
done(null, {email: user.email, roles : user.roles});
});
passport.deserializeUser(function(session, done) {
User.findOne({email: session.email}, function(err, user) {
done(err, user);
});
});
router.use(session({ secret: 'my super secret',name: 'my-id', resave: false, saveUninitialized: false }));
router.use(passport.initialize());
router.use(passport.session());
Furthermore if this ever goes into production you need a different session handler than express-session like MongoStore or Redis
The routes
/* GET home page. */
router.get('/', require('connect-ensure-login').ensureLoggedIn('login'), function (req, res, next) {
if (req.user) {
res.render('index');
} else {
res.redirect('/login');
}
});
router.get('/login', function (req, res, next) {
res.render('login');
});
router.post('/login', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login'
}));
router.get('/logout', function (req, res) {
req.logout();
res.render('logout');
});
I am trying to use passport local auth with sequelize . When I submit login form, the request/respond cycle never end and there is no error message in the terminal .
Here are all of my codes:
app.js
var Sequelize = require('sequelize'),
express = require('express'),
bodyParser = require('body-parser'),
cookieParser = require('cookie-parser'),
passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
User = require('./models/users');
........ and other imports.....
//route import , model injection
var auth = require('./routes/auth')(User);
.......................
app.use(session({
store: new RedisStore(),
secret: 'keyboard cat',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done) {
console.log(user);
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id).then(function(user){
done(null, user);
}).catch(function(e){
done(e, false);
});
});
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({where: {username: username}}).then(function(err, user) {
if (err) { return done(err); }
if (!user) {
console.log('Incorrect username.');
return done(null, false, { message: 'Incorrect username.' });
} else if (password != user.password) {
console.log('Incorrect password');
return done(null, false, { message: 'Incorrect password.' });
} else {
console.log('ok');
done(null, user);
}
});
}
));
and routes/auth.js :
var express = require('express'),
passport = require('passport');
var routes = function(User) {
var router = express.Router();
// routes for registration
router.route('/register')
.get(function(req, res) {
res.render('register');
})
.post(function(req, res) {
User.count().then(function(number) {
if (number >= 1) {
res.redirect('/auth/login');
} else {
User.create({
username: req.body.username,
password: req.body.password
});
res.redirect('/auth/login');
}
});
});
//routes for login
router.route('/login')
.get(function(req, res) {
res.render('login');
})
.post(function(req, res) {
passport.authenticate('local', { successRedirect: '/dashboard',
failureRedirect: '/auth/login' });
});
return router;
};
module.exports = routes;
Why does the request/response cycle never end?
Your current middleware definition for './login' POST is incorrect and does not send a response, which is why it doesn't end (until it times out).
Instead of calling passport.authenticate in a middleware function, the result of calling passport.authenticate should be used as middleware itself. I suggest the following:
router.route('/login')
.get(function(req, res) {
res.render('login');
})
.post(passport.authenticate('local', { successRedirect: '/dashboard',
failureRedirect: '/auth/login' });
);
See http://passportjs.org/docs/authenticate for an example.
Race condition in registration code
You didn't ask about this, but there is a race condition in your middleware for './register' POST.
User.create returns a promise for saving the created user. Until that promise is resolved there is no guarantee that the user exists in the backing datastore. However, immediately after calling create, your code redirects to the login endpoint which would query the database for that user.
Here is some code that avoids this problem:
User.create({ ... })
.then(function() {
res.redirect('/auth/login');
})
.catch(function(err) {
// Handle rejected promise here
})
The catch is included because it is always good practice to handle rejected promises and thrown exceptions.
When trying to implement the example code from the passport guide, I'm running into an issue where the most recently logged in user "replaces" all others. For example:
user1 logs in and leaves a note as user1
user2 logs in
now when user1 leaves a note, it's posted as user2
Do the user sessions need to be stored in a database with something like connect-mongo or does passport keep track of individual sessions? It seems like the API calls are always getting the req.user for the most recent user, regardless of which user makes it.
A similar question had a problem with the serializer. I'm not sure where my problem lies so I'll just post it all.
// Express setup
var http = require('http');
var express = require('express');
var app = express();
var signedIn = false;
// Mongoose setup
var mongoose = require('mongoose');
mongoose.connect('');
var UserSchema = mongoose.Schema({
username: String,
password: String
});
var NoteSchema = mongoose.Schema({
text: String,
user: String
});
// Used for password authorization
UserSchema.methods.validPassword = function (pwd) {
return (this.password === pwd);
};
var Users = mongoose.model('Users', UserSchema);
var Notes = mongoose.model('Notes', NoteSchema);
// Passport setup
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
// Passport Serialize
passport.serializeUser(function (user, done) {
done (null, user.id);
});
passport.deserializeUser(function (id, done) {
Users.findById(id, function (err, user) {
done (err, user);
});
});
// Use Local Strategy
passport.use(new LocalStrategy(
function(username, password, done) {
Users.findOne({ username: username }, function (err, user) {
if (err) {
return done(err); }
if (!user) {
return done(null, false, {message: 'Incorrect username' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password' });
}
console.log(user.username + " is signed in");
return done(null, user);
});
}
));
// App configuration
app.configure(function () {
app.set('port', process.env.PORT || 5000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.static(__dirname + '/public'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
})
// Passport Authentication
app.post('/login',
passport.authenticate('local', { successRedirect: '/notes',
failureRedirect: '/login',
/*failureFlash: true*/ })
);
// API
// Notes
app.get('/api/notes', function (req, res) {
Notes.find({}, function (err, note) {
if (err) {
res.send(err);
}
res.json(note);
})
})
app.post('/api/notes', function (req, res) {
Notes.create({
text : req.body.text,
user : req.user.username,
done : false
}, function (err, note) {
if (err) {
res.send(err);
}
Notes.find(function (err, note) {
if (err) {
res.send(err);
}
res.json(note);
})
})
})
app.delete('/api/notes/:note_id', function (req, res) {
Notes.remove({ _id : req.params.note_id },
function (err, req) {
if (err) {
res.send(err);
}
Notes.find(function (err, note) {
if (err) {
res.send(err);
}
res.json(note);
});
});
});
// Users
// Create New User
app.post('/api/users', function (req, res, next) {
Users.create({
username : req.body.username,
password : req.body.password,
done : false
}, function (err, user) {
if (err) {
res.send(err);
} else {
res.redirect('/login');
}
});
});
// Routes
app.get('/', function (req, res) {
res.render('login');
});
app.get('/login', function (req, res) {
res.render('login');
})
app.get('/newuser', function (req, res) {
res.render('newuser');
})
app.get('/notes', function (req, res) {
if (req.user != null) {
console.log(req.user);
res.render('index', { 'userName' : req.user.username });
} else {
res.send("Not signed in!")
}
});
// HTTP Server
http.createServer(app).listen(app.get('port'), function() {
console.log("Express server listening on: " + app.get('port'));
})
I can think of several reasons why this might happen:
when you (mistakenly) create a global variable to hold some form of state information, like your suspicious looking signedIn variable (you don't seem to be doing that in the posted code though);
using app.locals where you meant to be using res.locals (you're also not doing that, but still worth a mention);
you're logging in as a second user from a different tab/window from the same browser; sessions are shared across tabs/windows, so when you log in as UserA from one tab, and subsequently log in as UserB from another, you can't perform actions as UserA anymore before that session was overwritten by the UserB session; try logging in as UserB from a different browser;
Im using NodeJS, ExpressJS, Mongoose, passportJS & connect-ensure-login. Authenticating users works perfectly.
....
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn;
var app = express();
...
app.use(passport.initialize());
app.use(passport.session());
...
passport.use(new LocalStrategy({usernameField: 'email', passwordField: 'password'},
function(email, password, done) {
User.findOne({ 'email': email, 'password': password },
{'_id': 1, 'email':1}, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
return done(null, user);
});
}));
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
app.post('/login', passport.authenticate('local',
{ successReturnToOrRedirect: '/home', failureRedirect: '/login' }));
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
Now, I want to add restrictions to some routes to be accessible only by admin. How can I do that? e.g. /admin/*
var schema = new mongoose.Schema({
name: String,
email: String,
password: String,
isAdmin: { type: Boolean, default: false }
});
mongoose.model('User', schema);
Any hint? Thanks
You could attach a custom middleware to the /admin/* route that would check for admin status before passing the request on the any of the more specific /admin/ routes:
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn;
...
var requiresAdmin = function() {
return [
ensureLoggedIn('/login'),
function(req, res, next) {
if (req.user && req.user.isAdmin === true)
next();
else
res.send(401, 'Unauthorized');
}
]
};
app.all('/admin/*', requiresAdmin());
app.get('/admin/', ...);
//Add following function to your app.js above **app.use(app.router)** call;
//This function will be called every time when the server receive request.
app.use(function (req, res, next) {
if (req.isAuthenticated || req.isAuthenticated())
{
var currentUrl = req.originalUrl || req.url;
//Check wheather req.user has access to the above URL
//If req.user don't have access then redirect the user
// to home page or login page
res.redirect('HOME PAGE URL');
}
next();
});
I have not tried it but i think it will work.