I log in with the user at the login, and the user object gets saved for req.user (or passports user) however after I go to a different route/state it doesnt hold the user object there anymore. To demonstrate I just try to console.log(req.user) and it returns undefined, for a POST method (updateUserProfile) in my controller. I used PostMan to test the POST method, the GET method worked on Postman for grabbing all users. If I refresh the page the app.get(*) will load the req.user and print it fine, its just in the calls. What could be the reason? Could it be my express setup? Example:
My routes:
/**
* Routes for express app
*/
var express = require('express');
var users = require('../controllers/users');
var feedback = require("../controllers/feedbackapi");
var problem = require("../controllers/problemapi");
var pair = require("../controllers/userproblempairapi");
var mongoose = require('mongoose');
var _ = require('lodash');
var Header = require('../../public/assets/header.server');
var App = require('../../public/assets/app.server');
module.exports = function(app, passport) {
// user routes
app.post('/login', users.postLogin);
app.post('/signup', users.postSignUp);
app.get('/logout', users.getLogout);
app.post('/updateUserProfile', users.updateUserProfile);
// google auth
// Redirect the user to Google for authentication. When complete, Google
// will redirect the user back to the application at
// /auth/google/return
// Authentication with google requires an additional scope param, for more info go
// here https://developers.google.com/identity/protocols/OpenIDConnect#scope-param
app.get('/auth/google', passport.authenticate('google', { scope: [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email'
] }));
// Google will redirect the user to this URL after authentication. Finish the
// process by verifying the assertion. If valid, the user will be logged in.
// Otherwise, the authentication has failed.
app.get('/auth/google/callback',
passport.authenticate('google', {
successRedirect: '/',
failureRedirect: '/login'
}));
//Important** on refresh we look at our wildcard call to find out if we're still logged in.
// Retrieves all topics on any endpoint for demonstration purposes
// If you were indeed doing this in production, you should instead only
// query the Topics on a page that has topics
app.get('*', function(req, res, next) {
// We don't want to be seeding and generating markup with user information
var user = req.user ? { authenticated: true, isWaiting: false } : { authenticated: false, isWaiting: false };
// An object that contains response local variables scoped to the request, and therefore available only to the view(s) rendered during
// that request/response cycle (if any). Otherwise, this property is identical to app.locals
// This property is useful for exposing request-level information such as request path name, authenticated user, user settings, and so on.
// pass in data to be seeded into the TopicStore
res.locals.data = {
UserStore: { user: user }
};
next();
});
// This is where the magic happens. We take the locals data we have already
// fetched and seed our stores with data.
// App is a function that requires store data and url to initialize and return the React-rendered html string
app.get('*', function (req, res, next) {
var html = App(JSON.stringify(res.locals.data || {}), req, res);
html = html.replace("TITLE", Header.title)
.replace("META", Header.meta);
if(process.env.NODE_ENV === 'devhotloader') {
html = html.replace("LINK", '');
} else {
html = html.replace("LINK", Header.link);
}
res.contentType = "text/html; charset=utf8";
res.end(html);
});
};;
My users controller:
var _ = require('lodash');
var User = require('../models/user');
var passport = require('passport');
var ParsonsProblem = require('../models/parsonsproblem');
var Feedback = require('../models/feedback');
var UserProblemPair = require('../models/userproblempair');
/**
* POST /login
*/
exports.postLogin = function(req, res, next) {
// Do email and password validation for the server
/*var Feed = new Feedback({
description: 'Supsuop'
});
var Problem = new ParsonsProblem({
description: 'Test',
feedback: Feed
});
var Pair = new UserProblemPair({
problem_id: Problem,
attempt_quantity: 0,
completed: true
});
*/
console.log(req.body);
passport.authenticate('local', function(err, user, info) {
if(err) return next(err);
if(!user) {
req.flash('errors', {msg: info.message});
}
// Passport exposes a login() function on req (also aliased as logIn()) that can be used to establish a login session
req.logIn(user, function(err) {
if(err) return next(err);
req.flash('success', { msg: 'Success! You are logged in'});
res.end('Success');
console.log(req.user);
});
})(req, res, next);
/*
Feed.save(function(err) {console.log('Feedback saved');});
Problem.save(function(err) {console.log('Problem saved');});
Pair.save(function(err) {console.log('ProblemPair saved');});
console.log(Feed);
*/
};
/**
* POST UpdateUser Profile
*/
exports.updateUserProfile = function(req, res) {
var id = req.user._id;
if (req.body.firstName == "") {
req.body.firstName = req.user.profile.firstName;
}
if (req.body.lastName == "") {
req.body.lastName = req.user.profile.lastName;
}
if (req.body.gender == "") {
req.body.gender = req.user.profile.gender;
}
if (req.body.section == "") {
req.body.section = req.user.profile.section;
}
User.findById(id, function(err, user) {
console.log("ID: " + id);
user.profile.firstName = req.body.firstName;
user.profile.lastName = req.body.lastName;
user.profile.gender = req.body.gender;
user.profile.section = req.body.section;
user.save();
res.end();
});
}
/**
* GET /logout
*/
exports.getLogout = function(req, res, next) {
// Do email and password validation for the server
console.log("User has been logged out");
req.logout();
res.redirect('/');
//res.end():
};
/**
* POST /signup
* Create a new local account
*/
exports.postSignUp = function(req, res, next) {
var user = new User({
email: req.body.email,
password: req.body.password,
profile: {
firstName : req.body.firstName,
lastName : req.body.lastName,
section : req.body.section
}
});
//user.profile.firstName = req.body.firstName;
//user.profile.lastName = req.body.lastName;
//user.profile.section = req.body.section;
User.findOne({email: req.body.email}, function(err, existingUser) {
if(existingUser) {
req.flash('errors', { msg: 'Account with that email address already exists' });
res.redirect('/sign');
}
user.save(function(err) {
if(err) return next(err);
req.logIn(user, function(err) {
if(err) return next(err);
console.log('Successfully created');
console.log('Printing user');
console.log(user);
console.log('Print our body from our request');
console.log(req.body);
res.redirect('/');
});
});
});
};
Express Setup:
app.disable('x-powered-by');
app.set('views', path.join(__dirname, '..', 'views'));
app.set('view cache', false);
app.use(bodyParser.json({limit: '100mb'}));
app.use(bodyParser.urlencoded({extended: true})); // for parsing application/x-www-form-urlencoded
app.use(methodOverride());
app.use(express.static(path.join(__dirname, '../..', 'public')));
// I am adding this here so that the Heroku deploy will work
// Indicates the app is behind a front-facing proxy,
// and to use the X-Forwarded-* headers to determine the connection and the IP address of the client.
// NOTE: X-Forwarded-* headers are easily spoofed and the detected IP addresses are unreliable.
// trust proxy is disabled by default.
// When enabled, Express attempts to determine the IP address of the client connected through the front-facing proxy, or series of proxies.
// The req.ips property, then, contains an array of IP addresses the client is connected through.
// To enable it, use the values described in the trust proxy options table.
// The trust proxy setting is implemented using the proxy-addr package. For more information, see its documentation.
app.enable('trust proxy');
// Cookie parser should be above session
// cookieParser - Parse Cookie header and populate req.cookies with an object keyed by cookie names
// Optionally you may enable signed cookie support by passing a secret string, which assigns req.secret
// so it may be used by other middleware
app.use(cookieParser());
// Create a session middleware with the given options
// Note session data is not saved in the cookie itself, just the session ID. Session data is stored server-side.
// Options: resave: forces the session to be saved back to the session store, even if the session was never
// modified during the request. Depending on your store this may be necessary, but it can also
// create race conditions where a client has two parallel requests to your server and changes made
// to the session in one request may get overwritten when the other request ends, even if it made no
// changes(this behavior also depends on what store you're using).
// saveUnitialized: Forces a session that is uninitialized to be saved to the store. A session is uninitialized when
// it is new but not modified. Choosing false is useful for implementing login sessions, reducing server storage
// usage, or complying with laws that require permission before setting a cookie. Choosing false will also help with
// race conditions where a client makes multiple parallel requests without a session
// secret: This is the secret used to sign the session ID cookie.
// name: The name of the session ID cookie to set in the response (and read from in the request).
// cookie: Please note that secure: true is a recommended option.
// However, it requires an https-enabled website, i.e., HTTPS is necessary for secure cookies.
// If secure is set, and you access your site over HTTP, the cookie will not be set.
var sess = {
resave: true,
saveUninitialized: true,
// Use generic cookie name for security purposes
key: 'sessionId',
secret: secrets.sessionSecret,
// Add HTTPOnly, Secure attributes on Session Cookie
// If secure is set, and you access your site over HTTP, the cookie will not be set
cookie: {
expires: false,
httpOnly: false,
//secure: false
},
store: new MongoStore({ url: secrets.db, autoReconnect: true})
};
var node_env = process.env.NODE_ENV;
console.log('Environment: ' + node_env);
//if(node_env === 'production') {
//sess.cookie.secure = false; // Serve secure cookies
//}
app.use(session(sess));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
edit: Printing out the req.session and req.user only display when logging in and signing in/out, when transitioning to another view with react-router it doesn't have that info anymore. ("tried with console logs")
I think you have to user react-router to handle internal redirects.
Related
Let's say i have several routes in express like,
app.use('/login', login);
app.use('/blogs', blog);
app.use('/news', news);
app.use('/profile', profile);
Here, login, blog, news, profile is routes .js files which contains different get/post routes. I.E. /blogs/new or blogs/:id/edit etc.
Suppose I have 2 users in my system, A and B.
A can access /blogs and /news but can't access /profile
B can access /profile but not the other two.
And /login can be accessed by any user.
How do i do this with express and node?
Is there any way i can set a user to be permitted only to access /profile route and it's children route?
P.S i don't want to use passport.
You can use passport.js for this purpose. Here is an example code for adding passport to your application.
1)Add following lines to packages.json
"passport": "^0.4.0",
"passport-jwt": "^3.0.1",
2). And the following code in server.js. Passport.js has multiple strategies for authentication. Here is configuration for jwt
var passport = require('passport');
app.use(passport.initialize());
require('./server/config/passport')(passport);
var auth = passport.authenticate('jwt', { session: false });
app.use('/your/path/*', auth, function (req, res, next) {
next();
});
Passport with check all the paths like /your/path/* ( i hope you know that).
I will demonstrate this with the cookie session. first lets set up a session. i will be using mongod, for different databases, express-session has different packages to save the session.
//------ app.js----------
const session = require("express-session");
const MongoDbStore = require("connect-mongodb-session")(session);
const store = new MongoDbStore({
uri: "mongodb://localhost:27017/yilmaz",
collection: "sessions",
});
// ------ this adds "session" property to the req
app.use(
session({
secret: "anyhting long",
resave: false,
saveUninitialized: false,
store: store,
})
);
First step of authentication is signing up. We save user's email and password. Next step user logs in the system. In this step we check if the email (or username) and password are in match.
exports.postLogin = async (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
const user = await User.findOne({ email });
if (!user) {
return res.redirect("/login");
}
const match = await bcrypt.compare(password, user.password);
if (match) {
// when we protect the routes we look at this property
// session property is attached by the express-session
// -------THIS IS WHERE WE SAVE THE SESSION TO DATABASE
req.session.isLoggedIn = true;
req.session.user = user;
// after req.session.user is set, everytime client makes a request, our middleware will be checking this
return req.session.save((err) => {
console.log("error in postLogin", err);
return res.redirect("/");
});
}
res.redirect("/login");
};
with checking `req.session.isLoggedin` you can write a middleware and used on the routes.
module.exports = (req, res, next) => {
if (!req.session.isLoggedin) {
return res.redirect("/login");
}
next();
};
in any route you want u can add this middleware
const isAuth = require("../middleware/is-auth");
router.get("/edit-product/:productId", isAuth, adminController.getEditProduct);
I am building a REST server in nodejs using express.
I would like to allow certain users to perform certain calls.
i.e. have an admin who can edit other users and see reports, where a user can only perform simple actions.
I was trying to use passport.js and passport-ldapauth, and also I would like to perform different queries for authentication (check credentials) and authorization (check if the user is part of a group).
var fs = require('fs');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var passport = require('passport');
var LdapStrategy = require('passport-ldapauth');
var app = express();
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Allow self signed certificates
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
app.use('/', index);
app.use('/users', users);
var OPTS = {
server: {
url: 'ldaps://...',
bindDN: 'cn=Manager,dc=com',
bindCredentials: 'secret',
searchBase: 'ou=people,dc=com',
searchFilter: '(uid={{username}})',
tlsOptions: {
ca: [fs.readFileSync('/path/to/certificate.crt')]
}
},
handleErrorsAsFailures: true,
failureErrorCallback: (err) => console.log(err)
};
passport.use(new LdapStrategy(OPTS));
passport.use('test', new LdapStrategy(OPTS));
app.use(passport.initialize());
app.post('/login', function(req, res, next) {
passport.authenticate('ldapauth', function(err, user, info) {
if (err) return next(err);
if (!user) return res.status(401).send(info);
res.json(user);
// req.logIn(user, function(err) {
// if (err)
// console.error(err);
// if (err) return next(err);
// return res.json(user);
// })
})(req, res, next);
});
The passport-ldapauth strategy does not allow you to perform any additional checks or queries as far as I know from reading over the documentation. The strategy and Passport in general is aimed at making the login/authentication process seamless and easy as possible. So any additional constraints will need to be handled on your own.
With that said, passport-ldapauth utilizes ldapauth-fork underneath which in turn uses ldapjs. You can try to utilize ldapjs as shown here and here, but I think the easiest solution would be to use ldapauth-fork directly.
We first need to set up ldapauth-fork, so we'll use the following example app/ldap/index.js:
const LdapAuth = require('ldapauth-fork')
const ldap = new LdapAuth({
url: 'ldaps://...',
bindDN: 'cn=Manager,dc=com',
bindCredentials: 'secret',
searchBase: 'ou=people,dc=com',
searchFilter: '(uid={{username}})',
tlsOptions: {
ca: [fs.readFileSync('/path/to/certificate.crt')]
})
ldap.on('error', (err) => { throw err })
module.exports = ldap
Our example app/controllers/auth.js could look something like this:
const jwt = require('jsonwebtoken')
const ldap = require('../ldap')
const { User } = require('../database/models') // mongoose model
const Promise = require('bluebird')
exports.login = async (req, res) => {
const { username, password } = req.body
if (!username || !password) {
res.status(400
res.json({ error: 'Missing username or password.' })
return
}
// ldapauth-fork doesn't support Promises.
// You can try to promisfy it, but I prefer this.
// I've named it `profile`, but you can name it whatever you want.
const profile = await Promise.fromCallback(cb => ldap.authenticate(username, password, cb))
// Since this is a REST API, we need to send back a token.
// For this example, we're creating it by hand.
const token = jwt.sign({ user: profile }, 'secret', {})
// Use epoch time from the token instead of generating it ourselves.
const { exp } = jwt.verify(token, 'secret')
// Finally send the token.
// By convention, the keys are snake case.
res.json({
access_token: token,
token_type: 'Bearer',
expires_in: exp,
user: profile
})
}
Now that we have our token created, we need a way to verify that token. To do that we need to write our own middleware. So for example app/middleware/valid-token.js:
const jwt = require('jsonwebtoken')
exports.needsAdminAccess = (req, res, next) => {
// This token should have already been validated by the `requiresToken` middleware
let token = req.header('authorization').split(' ')[1]
token = jwt.verify(token, 'secret')
// Let's check if they are in the admin group
// Remember that we set the user/profile value in the controller.
if (!token.user.dn.includes('ou=ADMIN')) {
next(new Error('You must be an admin to access this route.'))
return
}
// Any additional checks would go here.
// ...
// If everything is fine then call next to let the request continue.
next()
}
exports.requiresToken = (req, res, next) => {
// Assuming the token is in the header as Authorization: Bearer token
let token = req.header('authorization').split(' ')[1]
// Make sure our secret key matches
token = jwt.verify(token, 'secret')
// Additional checks of the token should be done here as well.
// ...
// Don't forget to call next if all is good
next()
}
Finally we use the middleware wherever you define your routes, for example:
const express = require('express')
const app = express()
const { requiresToken, needsAdminAccess } = require('./middleware/valid-token')
// This route needs a valid token, but not admin rights
app.get('/user', requiresToken, (req, res) => { })
// This route needs a valid token AND admin rights
app.get('/admin', requiresToken, needsAdminAccess, (req, res) => { })
I wrote everything from scratch to hopefully paint a clear picture on how everything works. You can use other packages to validate the token for you, but we need to do verify specific things so we write our own.
I am novice to Node passport authentication. I completely write an example of passport authentication as below:
var express = require('express');
var passport = require('passport');
var passportLocal = require('passport-local');
/*
Since express doesn't support sessions by
default you need following middlewares.
*/
// For storing session ID in browser
var cookieParser = require('cookie-parser');
// For rendering credentials from request bodies
var bodyParser = require('body-parser');
/*
For server side storage of session information.
All these session information is in server memory.
If the machine app reboot all sessions infromation
will disapear. The information coming with request
, thanks to cookies, with the session information
stored in the server used in deserializing the users.
Be careful properly handle session information
in server farms, round robbining and load balancing etc.
But it is easy to configure express-session middleware
to use external storage. In this case we use local machine.
*/
var expressSession = require('express-session');
var app = express();
app.set('view engine', 'ejs');
// For sessions. Need before Passport middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(expressSession({
// This is the secret used to sign the session ID cookie.
secret: process.env.SESSION_SECRET || 'd7n5ihsxx9by8ydt',
resave: false,
saveUninitialized: false
}));
// Passport need 2 middlewares
app.use(passport.initialize());
app.use(passport.session());
/*
Strategies need to be told how to veryfy username and password
that is inside authorization header that is client is goint to send.
*/
function verifyCredentials(username, password, done) {
/*
Use crypto.pbkdf2(password, salt, iterations, keylen[, digest], callback)
For the real app.
*/
if (username === password) {
/*
done() first argument is Error
Second argument 'user' is any object required for your business logic.
But make it as much as smaller for fast serializing and deserializing.
*/
done(null, {
id : 123,
name: username,
role: 'Admin',
catogery : 'Operator'
});
} else {
done(null, null);
}
};
/*
Now we have to configure passport. Call the local strategy with
single function function(username, password, done) where done is callback
funciton.
*/
passport.use(new passportLocal.Strategy(verifyCredentials));
/*
passport.serializeUser serializes the 'user' object.
The callback function done, need a small piece of
'user' object which is required in deserializing.
In following case 'user.id' is saved to session
req.session.passport.user = {id:'..'}
*/
passport.serializeUser(function(user, done) {
done(null, user.id);
});
/*
In deserialize function you provide in first argument of
deserialize function that same key of user object that was
given to done function in serialize call. So your whole object is
retrieved with help of that key. that key here is id.
In deSerialize function that key is matched with in
memory array / database or any data resource.
*/
passport.deserializeUser(function(id, done){
/*
Query database or cache here! Example
User.findById(id, function(err, user) {
done(err, user);
});
*/
done(null, {
id : id,
name: id,
role: 'Admin',
catogery : 'Operator'
});
});
/*
Middleware for API's. 3rd party middlewares are available
to download and install if you want. Middleware is
nothing but a simple function with the following format.
'next' is callback function which is used to pass the request
to the next middleware inline.
*/
function ensureAuthenticated(req, res, next){
if(req.isAuthenticated()){
next();
} else {
res.status(403).json( {msg: '403 Forbidden'} );
}
}
app.get('/', function(req, res) {
res.render('index', {
/*
Express doesn't have isAuthenticated for req.
But passport add it to req. Also the 'user' object
added in done() callback is available through req.
*/
isAuthenticated : req.isAuthenticated(),
user : req.user
});
});
app.get('/login', function(req, res) {
res.render('login');
});
app.get('/logout', function(req, res) {
//Passport add logout method to request object
req.logout();
res.redirect('/');
});
/*
In this end point, the middleware passport.authenticate('local') is called.
passport.authenticate('local') returns a functon similar to ensureAuthenticated.
*/
app.post('/login', passport.authenticate('local'), function(req, res) {
res.redirect('/');
});
/*
Second endpoint for API's. The endpoint is authenticated
with a middleware which is between URI and function.
*/
app.get('/api/data', passport.authenticate('local'), function(req, res) {
res.json([
{name: 'My'},
{name: 'Kumara'}
]);
});
app.get('/api/data/me', function(req, res) {
res.json([
{name: 'My'},
{name: 'Kumara'}
]);
});
var port = process.env.PORT || 3000;
app.listen(port, function() {
console.log('Server is running on Port : ' + port);
})
My question:
I have two URI's:
/api/data
/api/data/me
I assumed that since I have authenticated /api/data it will automatically authenticate /api/data/me since /api/data part is already authenticated, /api/data/me is like a child of /api/data. But it's not. Does this mean do I have to authenticate each and every API's?
If not, how can I group set of API's in a single strategy?
A wildcard could be used to match everything under a certain path. For example:
app.get('/api/data/*', passport.authenticate('local'), function(req, res, next) {
next(); // call next matching route
});
app.get('/api/data/me', function(req, res) {
res.json([
{name: 'My'},
{name: 'Kumara'}
]);
});
This enables the passport middleware for all requests under /api/data/ so that the user will need to authenticate using the passport local strategy. Logic for individual endpoints under /api/data/ can then be implemented without including the passport middleware.
There's more information about express route matching on the user guide. http://expressjs.com/en/guide/routing.html
I have looked high and low and can't figure out why I am being re-routed to login rather than dashboard upon creating a user.
The idea is to automatically be logged into and be re-routed to the Dashboard when a new user is created.
I'm new to Node please help!
// Import Modules/Libraries =====================
// takes body of req and parse to whatever using post()/put()
var bodyParser = require('body-parser');
// add encryption library
var bcrypt = require('bcryptjs');
// add form input security csrf
var csrf = require('csurf');
// add express framework for node
var express = require('express');
// add ORM for mongodb
var mongoose = require('mongoose');
// add sessions library - BEST security options in this library
var sessions = require('client-sessions');
// Create / Use Module =====================
// sew express into the 'app'
var app = express();
// log Server message to show server is running
console.log("Yo Adrian!... Our Server is now running!");
// Connect to Database =====================
// connect to mongo
mongoose.connect('mongodb://localhost/newauth');
// Creating Users =====================
// create Schema variables for generating user id''s in the database
var Schema = mongoose.Schema;
// use mongoose to generate the user id
var ObjectId = Schema.ObjectId;
// Create Schema --
// define an the object called 'User'
// using mongoose model which essentially represents a user in a database
// id will be created by mongodb
var User = mongoose.model('User', new Schema({
id: ObjectId,
firstName: String,
lastName: String,
email: {type: String, unique: true},
password: String
}));
// Set the Template Engine =====================
// Use Jade view engine
app.set('view engine', 'jade');
// Misc. Adjustments =====================
// Cleanup HTML Minification
app.locals.pretty = true;
// Middelware =====================
// - Before running routes, parse the request(s)
// - When a request comes in body-parser turn it into an object for us
app.use( bodyParser.urlencoded({ extended: true }));
app.use(sessions({
cookieName: 'session',
secret: 'skjdhfkjhvntruieowoeijervhfncmsyeiwuirdjhjfhdjnmcneiuoasad', // used to encrypt & decrypt info from a session
duration: 30 * 60 * 1000, // amount of miliseconds before a cookie expires, (absolute limit - 30 min)
activeDuration: 5 * 60 * 1000 // by setting this it lengthens the session so user wont get kicked out
}));
app.use(csrf());
// GLOBAL Middleware =====================
app.use(function(req, res, next){
if(req.session && req.session.user){
User.findOne({email: req.session.user.email }, function(err, user){
if(user){
req.user = user;
delete req.user.password;
req.session.user = req.user;
res.locals.user = req.user;
}
next();
});
}else{
next();
}
});
// GLOBAL Functions =====================
function requireLogin(req, res, next){
if(!req.user){
res.redirect('/login');
}else{
next();
}
};
// Create Routes =====================
// Index Page
app.get('/', function(req, res){
res.render('index.jade');
});
// Register Page
// Get the info our user sends to us
// Inject CSRF encryption token into page/form
app.get('/register', function(req, res){
res.render('register.jade', { csrfToken: req.csrfToken() });
});
// Send it back to the browser with the data they sent us as json
app.post('/register', function(req, res){
// res.json(req.body); ** this was to test json in step one
// generate a bcrpyt hashed password
var hash = bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(10));
// Here we create a new instance of the User model/schema above.
var user = new User({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: hash // store hashe password in db
});
user.save(function(err){
if(err){
var err = "Something Bad Happend! Try again!";
if(err.code === 11000){
error = "That email is already taken, try another!";
}
res.render('register.jade', { error: error });
}else{
res.redirect('/dashboard');
}
});
});
// Login Page
app.get('/login', function(req, res){
res.render('login.jade', {csrfToken: req.csrfToken() });
});
app.post('/login', function(req, res){
User.findOne({email: req.body.email }, function(err, user){
console.log("Querying...");
// If it is not the user?
if(!user){
// then ender error!
res.render('login.jade', {error:'Invalid email or password.'});
console.log('Login attempt failed');
}else{
// otherwise check if passwords match using bcrypt function
if(bcrypt.compareSync(req.body.password, user.password)){
console.log("Attempting to login... ");
// if they match assign a session to a user
req.session.user = user;
console.log('req.session.user = ' + req.session.user);
// redirect to the dashboard
res.redirect('/dashboard');
console.log('Login attempt Successful');
}else{
// Otherwise if they dont match render error!
res.render('login.jade', {error:'Invalid email or password.'});
console.log('Login attempt failed altogether');
}
}
});
});
// Dashboard Page
app.get('/dashboard', requireLogin, function(req, res){
res.render('dashboard.jade');
});
// Redirected Routes
// Logout Page
app.get('/logout', function(req, res){
req.session.reset(); // Delete session
res.redirect('/');
});
// Listen on Port 3000 =====================
app.listen(3000);
Ok so I figured it out! I needed to create a function that added a new user and call that function in registration. See below revised code:
// Import Modules/Libraries =====================
// takes body of req and parse to whatever using post()/put()
var bodyParser = require('body-parser');
// add encryption library
var bcrypt = require('bcryptjs');
// add form input security csrf
var csrf = require('csurf');
// add express framework for node
var express = require('express');
// add ORM for mongodb
var mongoose = require('mongoose');
// add sessions library - BEST security options in this library
var sessions = require('client-sessions');
// Create / Use Module =====================
// sew express into the 'app'
var app = express();
// log Server message to show server is running
console.log("Yo Adrian!... Our Server is now running!");
// Connect to Database =====================
// connect to mongo
mongoose.connect('mongodb://localhost/newauth');
// Creating Users =====================
// create Schema variables for generating user id''s in the database
var Schema = mongoose.Schema;
// use mongoose to generate the user id
var ObjectId = Schema.ObjectId;
// Create Schema --
// define an the object called 'User'
// using mongoose model which essentially represents a user in a database
// id will be created by mongodb
var User = mongoose.model('User', new Schema({
id: ObjectId,
firstName: String,
lastName: String,
email: {type: String, unique: true},
password: String
}));
// Set the Template Engine =====================
// Use Jade view engine
app.set('view engine', 'jade');
// Misc. Adjustments =====================
// Cleanup HTML Minification
app.locals.pretty = true;
// Middelware =====================
// - Before running routes, parse the request(s)
// - When a request comes in body-parser turn it into an object for us
app.use( bodyParser.urlencoded({ extended: true }));
app.use(sessions({
cookieName: 'session',
secret: 'skjdhfkjhvntruieowoeijervhfncmsyeiwuirdjhjfhdjnmcneiuoasad', // used to encrypt & decrypt info from a session
duration: 30 * 60 * 1000, // amount of miliseconds before a cookie expires, (absolute limit - 30 min)
activeDuration: 5 * 60 * 1000 // by setting this it lengthens the session so user wont get kicked out
}));
app.use(csrf());
// GLOBAL Middleware =====================
app.use(function(req, res, next){
if(req.session && req.session.user){
User.findOne({email: req.session.user.email }, 'firstName lastName email data', function(err, user){
if(user){
req.user = user;
delete req.user.password;
req.session.user = req.user;
res.locals.user = req.user;
}
next();
});
}else{
next();
}
});
createUserSession = function(req, res, user) {
var cleanUser = {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
data: user.data || {},
};
req.session.user = cleanUser;
req.user = cleanUser;
res.locals.user = cleanUser;
};
// GLOBAL Functions =====================
function requireLogin(req, res, next){
if(!req.user){
res.redirect('/login');
}else{
next();
}
};
// Create Routes =====================
// Index Page
app.get('/', function(req, res){
res.render('index.jade');
});
// Register Page
// Get the info our user sends to us
// Inject CSRF encryption token into page/form
app.get('/register', function(req, res){
res.render('register.jade', { csrfToken: req.csrfToken() });
});
// Send it back to the browser with the data they sent us as json
app.post('/register', function(req, res){
// res.json(req.body); ** this was to test json in step one
// generate 10 character salt
var salt = bcrypt.genSaltSync(10);
// generate a bcrpyt hashed password
var hash = bcrypt.hashSync(req.body.password, salt);
// Here we create a new instance of the User model/schema above.
var user = new User({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: hash // store hashe password in db
});
user.save(function(err){
if(err){
var err = "Something Bad Happend! Try again!";
if(err.code === 11000){
error = "That email is already taken, try another!";
}
res.render('register.jade', { error: error });
}else{
console.log('Saved... Redirecting to Dashboard!!');
createUserSession(req, res, user);
res.redirect('/dashboard');
}
});
});
// Login Page
app.get('/login', function(req, res){
res.render('login.jade', { csrfToken: req.csrfToken() });
});
app.post('/login', function(req, res){
User.findOne({email: req.body.email }, function(err, user){
console.log("Querying...");
// If it is not the user?
if(!user){
// then ender error!
res.render('login.jade', {error:'Invalid email or password.'});
console.log('Login attempt failed');
}else{
// otherwise check if passwords match using bcrypt function
if(bcrypt.compareSync(req.body.password, user.password)){
console.log("Attempting to login... ");
// if they match assign a session to a user
req.session.user = user;
console.log('req.session.user = ' + req.session.user);
// redirect to the dashboard
res.redirect('/dashboard');
console.log('Login attempt Successful');
}else{
// Otherwise if they dont match render error!
res.render('login.jade', {error:'Invalid email or password.'});
console.log('Login attempt failed altogether');
}
}
});
});
// Dashboard Page
app.get('/dashboard', requireLogin, function(req, res){
res.render('dashboard.jade');
});
// Redirected Routes
// Logout Page
app.get('/logout', function(req, res){
req.session.reset(); // Delete session
res.redirect('/');
});
// Listen on Port 3000 =====================
app.listen(3000);
I am trying to use passport to authenticate users on the web page. Everything works fine, except when authentication fails and the passport redirects the user to the same rout, all the data on the form is lost. Is there a way to persist the data and pass them back to the form.
I have the following in routes.js
// =====================================
// SIGNUP ==============================
// =====================================
// show the signup form
app.get('/signup', function(req, res) {
// render the page and pass in any flash data if it exists
signup.isAuthenticated = req.isAuthenticated();
signup.user = req.user;
signup.message = req.flash('signupMessage');
res.render('signup', signup);
});
// process the signup form
app.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/', // redirect to the secure section
failureRedirect : '/signup', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
on my passport.js I have the following:
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
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 // allows us to pass back the entire request to the callback
},
// this function is used when signing up
function(req, email, password, done) {
// TODO: get the user from data
if(email == 'myemail#gmail.com') {
// user email already exists
console.log('user already exists !');
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
}
else {
// if there is no user with that email
// create the user
var newUser = { username : 'myemail#gmail.com', name : 'Name Surname' };
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
return done(null, newUser);
}
}));
and my server.js has the following:
// server.js
// set up ======================================================================
// get all the tools we need
var express = require('express');
var path = require('path');
var app = express();
var port = process.env.PORT || 3000;
// var mongoose = require('mongoose');
var passport = require('passport');
var flash = require('connect-flash');
var morgan = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var multer = require('multer');
var configDB = require('./config/database.js');
// configuration ===============================================================
// mongoose.connect(configDB.url); // connect to our database
require('./config/passport')(passport); // pass passport for configuration
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// set up our express application
app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.json()); // get information from html forms
app.use(bodyParser.urlencoded({ extended: false }));
// use multer to process multi-part requests and multer to save our files by default to /uploads/ directory
app.use(multer({
dest : path.join(__dirname, '/uploads/'),
limits : {
fieldNameSize : 200, // 200 bytes
files : 5, // 5 files
fileSize : 5194304000000, // 5 GB
fields : 50 // 50 fields on the form
}
}))
app.use(cookieParser()); // read cookies (needed for auth)
app.use(express.static(path.join(__dirname, 'public')));
// required for passport
app.use(session({
secret: 'mylongsecretpassphrase',
resave : true,
saveUninitialized : true
})); // 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
// routes ======================================================================
require('./app/routes.js')(app, passport); // load our routes and pass in our app and fully configured passport
// show error page if the resource is not found
app.use('*', function(req, res) {
res.render('page-error', {
title : 'myWeb - Page Error',
description : 'my web page',
keywords : 'keywords1, keywords2, keywords3'
});
});
// launch ======================================================================
app.listen(port);
console.log('Node listens on port ' + port);
any help would be greatly appreciated !
If you don't want to lose form data, you could use AJAX to send your form, and send a status 401 Unauthorized in case the auth fails. Passport sends the 401 by default so the following should work (untested, may contain typos) :
app.post('/login', function(req, res, next) {
passport.authenticate('local-signup',
function(req, res) {
// If this function gets called, authentication was successful. If not, your ajax call gets a 401 status and you can handle it in .fail()
res.redirect('/');
});
});
A bit of explanation from the passport website :
By default, if authentication fails, Passport will respond with a 401
Unauthorized status, and any additional route handlers will not be
invoked. If authentication succeeds, the next handler will be invoked
and the req.user property will be set to the authenticated user.
Instead of using the default callbacks like
passport.authenticate('local-signup', {
successRedirect : '/', // redirect to the secure section
failureRedirect : '/signup', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
})
you can use a custom callback, and pass whatever input vars you want via flash messages like so
router.post('/signup', function(request, response, next) {
passport.authenticate('local-signup', function(err, user, info) {
if (err)
return next(err);
if (!user) {
// Attach flash messages to keep user input
request.flash('emailInput', request.body.email);
request.flash('usernameInput', request.body.username);
return response.redirect('/signup');
}
// 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.
request.logIn(user, function(err) {
if (err)
return next(err);
return response.redirect('/profile');
});
})(request, response, next);
});
then, when redirected, you can send the flash messages to your view templates like normal
response.render('signup.ejs', {
signupMessage: request.flash('signupMessage'),
emailInput: request.flash('emailInput'),
usernameInput: request.flash('usernameInput')
});