I am new to web development and NodeJS. I am working on authentication using passport. It was a small app so I put check on each route request to check whether the user is authenticated or not; But I guess that technique won't be feasible for a big app.
I want to authenticate the whole app. I know that it has something to do with middleware as each request passes through middleware, but I can't figure out where. Any middleware related explanation would be appreciated.
Here is my code
const express = require('express');
const app = express();
const path = require('path');
const mongo = require('mongodb').MongoClient;
const cookieParser = require('cookie-parser');
const expressHandlebars = require('express-handlebars');
const expressValidator = require('express-validator');
const session = require('express-session');
const passport = require('passport');
const localStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const User = require('./models/user');
const Admin = require('./models/admin');
app.engine('handlebars', expressHandlebars({defaultLayout:'layout'}));
app.set('view engine', 'handlebars');
const port = 8888;
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(session({
secret: 'secret',
saveUninitialized: true,
resave: true
}));
app.use(passport.initialize());
app.use(passport.session());
//express Validator
app.use(expressValidator({
errorFormatter: function(param, msg, value) {
var namespace = param.split('.'),
root = namespace.shift(),
formParam = root;
while (namespace.length) {
formParam += '[' + namespace.shift() + ']';
}
return {
param: formParam,
msg: msg,
value: value
};
}
}));
app.use(express.static(__dirname));
mongoose.Promise = global.Promise;
const url = 'mongodb://localhost:27017/userDB';
mongoose.connect(url);
Here is my login functionality.
app.get("/login", function (req, res) {
const loginPath = path.join(__dirname, '/login.html');
res.sendFile(loginPath);
});
passport.use(new localStrategy({
usernameField: 'adminUsername',
passwordField: 'password',
session: false
},
function (adminUsername, password, done) {
Admin.getAdminByAdminUsername(adminUsername, function (err, admin) {
if (err) throw err;
console.log('getAdmin called');
if (!admin) {
console.log('Admin Not Found');
return done(null, false);
}
Admin.comparePassword(password, admin.password, function (err, isMatch) {
console.log('comparePassword called');
if (err) throw err;
if (isMatch) {
return done(null, admin);
} else {
console.log('Wrong Password!');
return done(null, false);
}
});
});
}));
passport.serializeUser(function (admin, done) {
done(null, admin.id);
});
passport.deserializeUser(function (id, done) {
Admin.getAdminById(id, function (err, admin) {
done(err, admin);
console.log('findById called');
});
});
app.post('/login', passport.authenticate('local', {
failureRedirect: '/login'}), function(req, res){
console.log('login called');
res.redirect('/');
});
function ensureAuthenticated(req, res, next){
console.log(req.isAuthenticated());
if (req.isAuthenticated()) {
return next();
} else {
res.redirect('/login');
}
}
Previously, this is how I put a check on each request. Here is an example.
app.get("/update", ensureAuthenticated, function (req, res) {
const updatePath = path.join(__dirname, '/update.html');
res.sendFile(updatePath);
});
ensureAuthenticated is a middleware that you can also use standalone, like this:
app.use(ensureAuthenticated);
Express will pass requests to middleware and route handlers in order of their declaration. That means that if you add the line above in front of other middleware and route handlers, it will always be called, for each request (but read below why you might not actually want that). That way, you don't have to add it explicitly to each request handler.
Typically though, you separate "requests that require authentication" from requests that don't. For instance, requests for static resources (client-side JS, CSS, HTML, etc) typically don't require authentication. That means that you need to declare the static handler before ensureAuthenticated:
app.use(express.static(__dirname));
app.use(ensureAuthenticated);
The same goes for requests that are part of the login process: the login page and login request handler, for the simple reason that requiring a user to be logged in before they can access the login page doesn't make sense.
So the overall structure of your middleware/route handlers would look like this:
Common middleware (body parser, cookie handler, passport middleware, etc)
app.use(express.static(...))
Login routes
app.use(ensureAuthenticated)
"Protected" routes
Related
my passport strategy is never called. I recently added passport to my project and am using Boxes API to log a user in. I have done the example problem in another project and gotten it to work (https://github.com/smithdavedesign/OAUTH-Passport-BoxAPILogin-Example/blob/master/login/BoxLogin.js), however when I am trying to integrate it into my current project something has gone haywire. I read sessions and passport session are different and should not cause an issue, that being said I trying to figure out what middle ware is causing the issue. The box API is working correctly and returning me to my dashboard on the callback, however passport seams to be being skipped.
var products = require("../controllers/products");//get functions from product like add update delete ect
var validations = require("../controllers/validations");//get functions from product like add update delete ect
var settings = require("../config/settings");// get all things from settings we need
var Promise = require("bluebird");// adding promise mechanism
var bodyParser = require('body-parser');
var express = require('express');//web framework for node
const expressLayouts = require('express-ejs-layouts');// ejs view engine
const flash = require('connect-flash');//for error and success messages
const session = require('express-session');// holds data of users after login
const uuid = require('uuid')
var MemoryStore = require('memorystore')(session)//https://www.npmjs.com/package/memorystore
const util = require('util');
var os = require("os");
var hostname = os.hostname();//This is the users computer name
const multer = require('multer');
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')
},
filename: function (req, file, cb) {
cb(null, file.originalname)
}
})
const upload = multer({ storage: storage })
var path = require('path');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var passport = require('passport');
var BoxStrategy = require('passport-box').Strategy;
const app = express();//create instance of express
var BOX_CLIENT_ID = "**";
var BOX_CLIENT_SECRET = "**";
// Passport middleware
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function (user, done) {
console.log("serializeUser")//NEVER BEING CALLED
done(null, user);
});
passport.deserializeUser(function (obj, done) {
console.log("deserializeUser")//NEVER BEING CALLED
done(null, obj);
});
passport.use(new BoxStrategy({
clientID: BOX_CLIENT_ID,
clientSecret: BOX_CLIENT_SECRET,
callbackURL: "http://localhost:9000/dashboard"
},
function (accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
return done(null, profile);
});
}
));
//EJS
app.use(expressLayouts);
app.set('view engine', 'ejs');//view engine
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(
session({
genid: (req) => {
console.log('Inside session middleware genid function')
console.log('Request object sessionID from client: ' + req.sessionID);
var new_sid = uuid.v4();
console.log('New session id generated: ' + new_sid);
return new_sid; // use UUIDs for session IDs
},
store: new MemoryStore({
checkPeriod: 86400000 // prune expired entries every 24h
}),
secret: 'secret',
resave: true,
httpOnly: false,
saveUninitialized: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000
}
})
);
// Connect flash
app.use(flash());
// Global variables
app.use(function (req, res, next) {//diferent colors
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
res.locals.error = req.flash('error');
next();
});
//routes
app.use('/', require('./GraphRoutes'));
app.use('/', require('./dashboardRoutes'));
app.use('/', require('./index'));
app.use('/', require('./extraProccess'));
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static('public'));//public folder where all templates live
app.use(express.static("."));
app.get('/', function (req, res) {
res.render('index', { user: req.user });
});
app.get('/login', function (req, res) {
res.render('login', { user: req.user });
});
app.get('/auth/box', passport.authenticate('box'), function (req, res) {
console.log("box call")
});
app.get('/auth/box/callback',
passport.authenticate('box', { failureRedirect: '/login' }),
function (req, res) {
res.redirect('/dashboard');
});
app.get('/logout', function (req, res) {
req.logout();
res.redirect('/login');
});
I was calling my routes before initializing passport, as well as having cookie parser and other middle ware not in the correct spot.
today I was trying to get a passport authentication working. The email and password is static now but I will change that later. I have a lot of debug messages but only the ones outside of the Strategy. No errors or warnings regarding passport are displayed.
I have already tried to use different body parser modes (extented = true, extented = false).
Strategy
const LocalStrategy = require('passport-local').Strategy;
module.exports = function(passport) {
passport.use(
new LocalStrategy((email, password, done) => {
console.log('Authentication started');
var user = null;
if(email == 'test#mytest.com') {
if(password == 'test') {
user = {
email
}
console.log('Authenticated')
return done(null, user);
}
}
console.log('Error')
return done(null, user, {message: 'EMail or Password was wrong'});
})
);
passport.serializeUser(function(user, done) {
done(null, user.email);
});
passport.deserializeUser(function(id, done) {
done(err, user);
});
};
app.js (contains only important parts)
const express = require('express');
const expressSession = require('express-session')
const bodyParser = require('body-parser');
const expressLayouts = require('express-ejs-layouts');
const app = express();
const https = require('https');
const http = require('http');
app.use(expressSession({ secret: 'secret' }));
// Body Parser
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
// Passport
const passport = require('passport');
require('./config/passport')(passport);
app.use(passport.initialize());
app.use(passport.session());
// View Engine
app.set('view engine', 'ejs');
app.use(expressLayouts);
app.get('/applications', (req,res) => {
res.render('applications', {
user: req.user
});
});
app.post('/applications', (req, res, next) => {
console.log(req.body);
passport.authenticate('local', {
successRedirect: '/applications',
failureRedirect: '/',
failureFlash: false
})(req, res, next);
});
https.createServer(httpsOptions, app)
.listen(7443, () => {
console.log('HTTPS Server started on Port 7443')
});
http.createServer(app)
.listen(7080, () => {
console.log('HTTP Server started on Port 7080')
});
Make sure you are using the proper fields in your POST request. I noticed that in your strategy, you use the variables email and password. While your variable names aren't important, the fields you send in your POST request are. By default, passport-local uses the POST fields username and password. If one of these fields aren't present, the authentication will fail. You can change this to use email instead like so:
passport.use(
new LocalStrategy({
usernameField: 'email'
}, (email, password, done) => {
console.log('Authentication started');
// Your authentication strategy
})
);
Assuming you have the right POST fields, in order to use req.user in requests, you must have properly set up your passport.deserializeUser function. Testing your code, the authentication strategy is working fine for me, however I receive a reference error upon deserializeUser.
Yes, I see that this question has been asked here and here, but the answers point to either making changes to your deserializeUser callback, or changing something in your Mongoose model.
I've tried the first to no avail and am using the regular ol' NodeJS driver, so I'm not quite sure where to pinpoint the root cause of my issue.
Here's my script:
AUTHENTICATE.JS:
var app = require('express');
var router = app.Router();
var assert = require('assert');
var bcrypt = require('bcrypt');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
},
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.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
router.post('/login',
passport.authenticate('local', {successRedirect:'/',
failureRedirect:'/login',failureFlash: false}),
function(req, res) {
res.redirect('/');
});
For my app.js file I've tried a number of the fixes suggested in similar questions but nothing has made a difference. This is what it looks like:
APP.JS:
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var expressValidator = require('express-validator');
var passport = require('passport');
var localStrategy = require('passport-local').Strategy;
var mongo = require('mongodb').MongoClient;
var session = require('express-session');
var bcrypt = require('bcrypt');
// Express Session
app.use(session({
secret: 'keyboard cat',
saveUninitialized: true,
resave: true,
cookie: {secure: true}
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
//initialize passport for app
app.use(passport.initialize());
app.use(passport.session());
// make mongodb available to the application
app.use((req, res, next) => {
mongo.connect('mongodb://localhost:27017/formulas', (e, db) => {
if (e) return next(e);
req.db = db;
next();
});
});
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
app.use(express.static(__dirname + '/public'));
// Express Validator
app.use(expressValidator({
errorFormatter: function(param, msg, value) {
var namespace = param.split('.')
, root = namespace.shift()
, formParam = root;
while(namespace.length) {
formParam += '[' + namespace.shift() + ']';
}
return {
param : formParam,
msg : msg,
value : value
};
}
}));
//define routes here
app.set('port', (process.env.PORT || 3000));
app.listen(app.get('port'), function(){
console.log("The server is now listening on port "+app.get('port'));
});
module.exports = app;
Any help would be greatly appreciated, thank you.
As both questions you included point out, you need to define the User class first before you can use it. It's not something that passportjs or expressjs provide, you need to implement it for yourself or use another module that gives you that functionality (like mongoose).
In the first SO link you shared the answer suggested the OP implement a user model (User) in mongoose (it seems to be a popular choice).
The second links answer simplified things a bit by adding a hard-coded object to represent the user in the deserializer function.
i'm developing application on cloud9 enviroment. using:
node 4.43
express 4.13.4
i have integrated my Demo Auth0 account into my on-dev application.
i am able to login (being redirected to the first page of my app), but when i'm printing req.isAuthenticated() i'm getting false. also req.user is undefined.
i have followed the quick start of auth0 for node.js.
i'm attaching the three files that are mainly invovled:
app.js:
var express = require('express'),
app = express(),
BodyParser = require("body-parser"),
mongoose = require("mongoose"),
student = require ("./models/student"),
students_class = require("./models/class"),
// =============
// auth0
// =============
passport = require('passport'),
strategy = require('./models/setup-passport'),
cookieParser = require('cookie-parser'),
session = require('express-session');
app.use(cookieParser());
app.use(session({ secret: 'FpvAOOuCcSBLL3AlGxwpNh5x-U46YCRoyBKWJhTPnee2UELMd_gjdbKcbhpIHZoA', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
app.get('/login',passport.authenticate('auth0', { failureRedirect: '/url-if-something-fails' }),
function(req, res) {
res.send(req.user);
if (!req.user) {
throw new Error('user null');
}
res.redirect("/", {username: req.user});
});
mongoose.connect("mongodb://localhost/myapp");
// ============================
// routes
// ============================
var classRoutes = require("./routes/class"),
indexRoutes = require("./routes/index"),
studentRoutes = require("./routes/student"),
assocRroutes = require ("./routes/assoc");
// ============================================
// configuring the app
// ============================================
app.set("view engine", "ejs");
app.use(express.static ("public"));
app.use(BodyParser.urlencoded({extended: true}));
app.use(classRoutes);
app.use (indexRoutes);
app.use(studentRoutes);
app.use(assocRroutes);
app.listen(process.env.PORT, process.env.IP, function() {
console.log('Attendance Server is Running ....');
});
setup-passport.js
var passport = require('passport');
var Auth0Strategy = require('passport-auth0');
var strategy = new Auth0Strategy({
domain: 'me.auth0.com',
clientID: 'my-client-id',
clientSecret: 'FpvAOOuCcSBLL3AlGxwpNh5x-U46YCRoyBKWJhTPnee2UELMd_gjdbKcbhpIHZoA',
callbackURL: '/callback'
}, function(accessToken, refreshToken, extraParams, profile, done) {
// accessToken is the token to call Auth0 API (not needed in the most cases)
// extraParams.id_token has the JSON Web Token
// profile has all the information from the user
return done(null, profile);
});
passport.use(strategy);
// This is not a best practice, but we want to keep things simple for now
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
module.exports = strategy;
index.js (the actual fisrt page where i want to re-direct after successful login:
var express = require("express");
var passport = require('passport');
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn();
var router = express.Router();
var student = require ("../models/student");
//INDEX
router.get("/callback", function(req, res) {
student.find({}, function(err, student) {
console.log(req.isAuthenticated())
if (err) {
console.log(err);
} else {
res.render("home/index.ejs", {
students: student
});
}
});
});
module.exports = router;
any suggestion what could go wrong?
also wierd for me that on app.js, the guide is initializing the variable strategy but actually never seem to use it.
BUMP
You are not calling passport.authenticate() in the /callback endpoint. See for comparison: https://auth0.com/docs/quickstart/webapp/nodejs#5-add-auth0-callback-handler
// Auth0 callback handler
app.get('/callback',
passport.authenticate('auth0', { failureRedirect: '/url-if-something-fails' }),
function(req, res) {
if (!req.user) {
throw new Error('user null');
}
res.redirect("/user");
});
I have a server in node.js using express and passport with the passport-local strategy.
I have the users in the database and through passport I'm able to authenticate them, unfortunately when a second request comes from the same client the req.isAuthenticated() method returns false.
There is also no user in the request (req.user = undefined).
I've also checked and when doing the authentication although I get back a user from passport.authenticate('local'... I do not get req.user populated then. If I try to set it up manually it just doesn't propagate for following requests.
I don't understand what I'm doing wrong, here is my code.
server.js
var express = require('express'),
compass = require('node-compass'),
routes = require('./server/routes')
http = require('http'),
path = require('path'),
passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
Database = require('./server/repositories/database'),
Configuration = require('./server/config').Config,
crypto = require('crypto');
var app = express();
app.enable("jsonp callback");
passport.use(new LocalStrategy(
function(email, password, done) {
process.nextTick(function () {
var userService = new UserService();
userService.login(email, crypto.createHash('md5').update(password).digest("hex"), function(error, user) {
if (error) done(error, user);
else if (!user) return done(null, false, { message: 'wrong credentials'});
return done(null, user);
});
});
}
));
passport.serializeUser(function(user, done) {
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
var userService = new UserService();
userService.findById(id, function(err, user) {
done(err, user);
});
});
app.configure(function(){
app.set('port', Configuration.Port);
app.set('views', __dirname + '/app/views');
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(compass({
project: path.join(__dirname, 'app'),
sass: 'styles'
}));
app.use(express.session({ secret: 'keyboard cat' }));
app.use(function(err, req, res, next){
console.error(err.stack);
res.send(500, 'Something broke!');
});
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'app')));
});
routes.configure(app);
Database.open(function() {
app.listen(Configuration.Port, function() {
console.log("Express server listening on port " + Configuration.Port);
});
});
routes.js
var Configuration = require('./config').Config;
var ApiResult = require('../model/apiResult').ApiResult;
var ApiErrorResult = require('../model/apiErrorResult').ApiErrorResult;
var ApiReturnCodes = require('../model/apiReturnCodes').ApiReturnCodes;
var passport = require('passport');
var usersController = require('./controllers/usersController');
exports.configure = function(app) {
function ensureAuthenticated(req, res, next) {
console.log(req.isAuthenticated());
if (req.isAuthenticated()) { return next(); }
else {res.send(new ApiErrorResult(ApiReturnCodes.NOT_LOGGED_IN, null));}
}
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err || !user) { console.log(info); res.send(new ApiErrorResult(ApiReturnCodes.ENTITY_NOT_FOUND, null)); }
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user
else res.send(new ApiResult(user));
})(req,res,next);
});
app.get('/anotherLink', ensureAuthenticated, function(req, res, next) {
res.json({Code:0});
});
}
When I hit the link /anotherLink after being authenticated I get res.isAuthenticated() as false.
Also when I see the req.session after the ensureAuthenticated is called I get:
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport: {} }
What am I missing for it to save the information that that user is authenticated?
On the client side I'm using Angular only doing a simple get with the url without parameters.
If I forgot to put something here just tell me, I'll update it.
Any help will be greatly appreciated. Thanks
So I found out what was wrong with my code.
My passport.deserializeUser method used the method userService.findById
And that called the repository... like this:
userRepository.findUnique({"_id": id}, callback);
because the id was generated by MongoDB the correct call needs to be:
userRepository.findUnique({"_id": new ObjectID(id)}, callback);
I hope this saves some time to the next person with the same problem.
With this detail, this code should work nicely for everyone wanting to use the LocalStrategy on the Passport framework.