Calling and Saving User Account Custom Data - node.js

I'm making a user account system for my new website using node.sdk,stormpath,express.js and passport.js . So I've set up an account with a custom data slot. I would like to know how can I post new data to this custom data slot when they log out and retrieve it when they log in.I'm new to using node and I don't know where to put my code or how to access the 'user' account info when they have logged in. From what I can tell passport.js is handling authentication so I probably can't see the users email to search for their user account url on the stormpath api... maybe I'm missing something here??
router.post('/register', function(req, res) {
var username = req.body.username; var password = req.body.password;
// Grab user fields. if (!username || !password) { return res.render('register', { title: 'Register', error: 'Email and password required.' }); }
// Initialize our Stormpath client. var apiKey = new stormpath.ApiKey( process.env['STORMPATH_API_KEY_ID'], process.env['STORMPATH_API_KEY_SECRET'] ); var spClient = new stormpath.Client({ apiKey: apiKey });
var app = spClient.getApplication(process.env['STORMPATH_APP_HREF'], function(err, app) { if (err) throw err;
account = {
givenName: 'John',
surname: 'Smith',
username: username,
email: username,
password: password,
customData:{
favList:'',
},
};
app.createAccount(account, function (err, createdAccount) {
if (err) {
return res.render('register', {'title': 'Register', error: err.userMessage });
} else {
passport.authenticate('stormpath')(req, res, function () {
return res.redirect('/home');
});
}
});
});
});
// Render the login page. router.get('/login', function(req, res) {
res.render('login', { title: 'Login', error: req.flash('error')[0] }); });
// Authenticate a user. router.post( '/login',
passport.authenticate( 'stormpath', { successRedirect: '/home', failureRedirect: '/login', failureFlash: 'Oops I guess you need an account to get in here..Soz', } ) );
// Render the dashboard page. router.get('/home', function (req, res) { if (!req.user || req.user.status !== 'ENABLED') { return res.redirect('/login'); }
res.render('home', { title: 'Home', user: req.user, } ); });

This is a great question. Thankfully the Passport API has you covered. You want to use a "Custom Callback" function, then you can get access to the user inside of that function. In the case of the Stormpath strategy the user object will be a Stormpath Account instance. However you will need to re-implement some of the redirection logic that you're currently passing in as options. Here is an example of how that would look with the Stormpath strategy:
app.post('/login', function(req, res, next) {
passport.authenticate('stormpath', function(err, user, info) {
if (err) {
return next(err);
}
else if (user) {
console.log('The account is: ', user);
req.logIn(user, function(err) {
if (err) {
next(err);
}else{
res.redirect('/dashboard');
}
});
}else{
req.flash('error',info.message);
res.redirect('/login');
}
})(req, res, next);
});
The docs for this custom strategy can be found here: http://passportjs.org/guide/authenticate/
Another note: I'd suggest creating your spClient outside of the route handler. The Stormpath Client can be used for multiple requests and only needs to be created once per process.

Related

Passport.js authenticate failureRedirect with parameters

Working on Passport.js authentication and want to display messages to client when login is not successful.
My current logic:
app.route('/login').post(passport.authenticate('local', { failureRedirect: '/' }),
(req, res) => {
res.redirect('/profile')
});
Also using similar thing in registeration which is working as I intended using connect-flash to display my message.
app.route('/register')
.post((req, res, next) => {
myDataBase.findOne({ username: req.body.username }, function (err, user) {
if (err) {
next(err);
} else if (user) {
req.flash('message', 'Username is already registered, please try different username');
res.redirect('/');
} else {........
How can I apply similar solution to my login logic? I suppose I need to create a custom callback function but could not figure it out.

How to redirect based on user role with PassportJS?

I need to redirect 3 types of users (based on a single user model schema). How can I do this within my router.post() call in the users route? I understand that passport.authenticate takes in certain parameters but I am wondering if there is any way around it to have multiple redirects based on the user type (role in the schema)? Many thanks!
here is how I am doing it for one type of users:
//////this is my passport.js file//////
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
// Load User model
const User = require('../models/User');
module.exports = function(passport) {
passport.use(
new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
// Match user
User.findOne({
email: email
}).then(user => {
if (!user) {
return done(null, false, { message: 'That email is not registered' });
}
// Match password
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Password incorrect' });
}
});
});
})
);
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
};
/////////auth.js file/////////
module.exports = {
ensureAuthenticated: function(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
req.flash('error_msg', 'Please log in to view that resource');
res.redirect('/users/login');
},
forwardAuthenticated: function(req, res, next) {
if (!req.isAuthenticated()) {
return next();
}
}
};
//////users.js route bellow/////////
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const passport = require('passport');
// Load User model
const User = require('../models/User');
const { forwardAuthenticated } = require('../config/auth');
const mongoose = require('mongoose');
// Login Page
router.get('/login', forwardAuthenticated, (req, res) => res.render('login'));
// Register Page
router.get('/register', forwardAuthenticated, (req, res) => res.render('register'));
// Register
router.post('/register', (req, res) => {
const { role, name, email, password, password2 } = req.body;
let errors = [];
if (!role || !name || !email || !password || !password2) {
errors.push({ msg: 'Please enter all fields' });
}
if (password != password2) {
errors.push({ msg: 'Passwords do not match' });
}
if (password.length < 6) {
errors.push({ msg: 'Password must be at least 6 characters' });
}
if (errors.length > 0) {
res.render('register', {
errors,
role,
name,
email,
password,
password2
});
} else {
User.findOne({ email: email }).then(user => {
if (user) {
errors.push({ msg: 'Email already exists' });
res.render('register', {
errors,
role: role,
name,
email,
password,
password2
});
} else {
const newUser = new User({
role,
name,
email,
password
});
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then(user => {
req.flash(
'success_msg',
'You are now registered and can log in'
);
res.redirect('/users/login');
})
.catch(err => console.log(err));
});
});
}
});
}
});
// Login
router.post('/login', (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/aboutus',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
});
// Logout
router.get('/logout', (req, res) => {
req.logout();
req.flash('success_msg', 'You are logged out');
res.redirect('/users/login');
});
module.exports = router;
I am pretty sure that on post you can do something like this :
app.post('/login', (req, res) => {
if (req.body.role === 'normalUser') {
res.redirect('/normalUserPage')
} else if (req.body.role === 'administrator') {
res.redirect('/administratorPage');
} else if (req.body.role === 'receptionist') {
res.redirect('/receptionistPage');
}
});
I am pretty sure that this should do, make sure that these conditions come after trying to
sign in with the email and password provided.
What you pretty much have to do is set a condition for the three users, and based on the user type you can redirect them to their specific page.
another solution is to log in with the provided information and display the same page. However, based on the users' role, you can specify the content they are viewing.
example :
if the user type is admin, expose a div that allows them to view all the data with the ability to edit or delete.
if the user is normal, expose a div that allows them to view only the content specified for them. For instance only the names without any other information.
if the user is a receptionist expose a div that allows them to view all the content but without the ability to edit any information.
I really hope this helps you. let me know if this does help you.
If it does not help you I can try to figure out another solution.
This is an old thread, but for those that stumble across it...I had the same issue and this is how I addressed it:
In users.js route, include
router.get('/redirectLogin', (req, res) => {
if(req.user.role === role){ //replace role with whatever you're checking
res.render('dashboardOne.ejs')
} else {
res.render('dashboardTwo.ejs')
}
})
Then in the login POST method, change successRedirect to:
// Login
router.post('/login', (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/users/redirectLogin',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
});
Remember that passport.deserializeUser() attaches the user object to req.user, which allows you to access any attributes of that object.

Setting up Global sessions by using Passport Consumer strategy

I need some help with setting up Passport Consumer Strategy and integrating it into "locals" (Right now, we have the local strategy working just fine). We have tried several approaches but with no luck on it working 100%. The below code is not the complete code, we have taken out some of it so this post doesn't get too long. Any help with this would greatly be appreciate. There could be compensation as well if someone can get us over this hurdle.
So one question is, if the user is authenticated by the consumer key and secret, how does Passport store the session variables so they are used throughout the site?
Second question, how do we handle the user after it passes the authentication process?
Both local and consumer need to be working.
Consumer key and secret using a POST by a Provider <- I can show some of the post if needed.
This needs to be OAuth1 Only, as of right now, OAuth2 isn't an option.
This is for a single sign-on authentication.
I can supply a consumer session output if needed.
Ultimately, we would like the local strategy and the consumer strategy working with the same "locals" global variables. As far as I can tell, we can authenticate the consumer, retrieve the user from our DB, create a session and can tell if user is "ensureAuthenticated".
Here is what we have working right now.
Local strategy is authenticating correctly.
We render the pages with these local variables:
"Omitted most of the code to save time."
//=================================================================
// The Authentication Module will bind to POST /login route
//=================================================================
authentication.initLocalStrategyRoutes(app);
passport.authenticate('local', {successReturnToOrRedirect: '/', failureRedirect: '/login'});
...
function renderPage(req, res, pageName, pageTitle){
res.render(pageName, {
pageName: pageTitle,
username: req.user ? req.user.username : '',
...
Consumer strategy is authenticating by a POST request from a "Provider"
We have tried adding the Consumer strategy to the authentication.
server.js
//=================================================================
// The Authentication Module will bind to POST /login route
//=================================================================
authentication.initLocalStrategyRoutes(app);
ADDED -> authentication.initConsumerStrategyRoutes(app);
passport.authenticate('local', {successReturnToOrRedirect: '/', failureRedirect: '/login'});
ADDED -> passport.authenticate('consumer', {successReturnToOrRedirect: '/', failureRedirect: '/login'});
authentication.js (omitted code)
module.exports = function(siteConfig, defaultRedirectPage, server, sessionStore, log) {
var passport = require('passport')
...
, ConsumerStrategy = require('passport-http-oauth').ConsumerStrategy
, TokenStrategy = require('passport-http-oauth').TokenStrategy
, LocalStrategy = require('passport-local').Strategy;
var auth = {};
var authenticationRedirects = { successRedirect: '/', failureRedirect: '/login' };
passport.serializeUser(function(user, done) {done(null, user);});
passport.deserializeUser(function(obj, done) {done(null, obj);});
auth.authenticate = function(email, password, callback) {
email = email.toLowerCase();
userController.findUserByUsernameWithPermissions(email,
function(err, user) {
if (err) return callback(err);
if (!user) return callback(null, null, 'Incorrect username.');
bcrypt.compare(password, user.password_hash, function(err, res) {
if(err){return callback(err);
} else if (!res) {return callback(null, null, 'Incorrect password.');
} else {if (user.account_state>0) {callback(null, user);} else {return callback(null, null, '/reset?rand='+user._id);}}
});
}
);
}
auth.initLocalStrategyRoutes = function(app){
passport.use(new LocalStrategy(auth.authenticate));
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) return next(err);
if (!user) return res.send({success: false, message: info});
req.logIn(user, function(err) {
if (err) { return next(err); }
res.send(req.user);
});
}) (req, res, next);
});
}
auth.initConsumerStrategyRoutes = function(app){
// passport.use(new LocalStrategy(auth.authenticate));
console.log('app: ', app)
passport.use('consumer', new ConsumerStrategy(
function(key, done) { console.log('starting ConsumerStrategy');
dbConsumerKey.findByConsumerKey({consumerKey: key}, function(err, consumerKey) {
if (err) { return done(err); }
if (!consumerKey) {
var errCode = dbError.find({name:'no_resource_link_id'}, function(err, errorCodes) {
console.log('statusText: ', errorCodes[0]["statusText"]);
return errorCodes[0]["statusText"];
});
return done(null, errCode);
} else {
if (!consumerKey[0]["consumerKey"]) { return done(err); }
if (!consumerKey[0]["consumerSecret"]) { return done(err); }
// return done(null, consumerKey[0]["consumerKey"], consumerKey[0]["consumerSecret"]);
return done(null, consumerKey[0], consumerKey[0]["consumerSecret"]);
}
});
},
function(requestToken, done) {
dbRequestTokens.find(requestToken, function(err, token) {
console.log('inside requestToken');
if (err) { return done(err); }
var info = { verifier: token.verifier,
clientID: token.clientID,
userID: token.userID,
approved: token.approved
}
done(null, token.secret, info);
});
},
function(timestamp, nonce, done) {
done(null, true)
}
));
};
auth.initTokenStrategyRoutes = function(app){}
auth.addUser = function(username, email, password, callback){auth.authenticate(username, "pass", callback);}
return auth;
};
The authentication.js strategy does validate the consumer key and secret. but it doesn't create the session variable we are wanting. We would like the consumer strategy code to be in the authentication.js file.
Now here is another approach, we created a separate files called consumerkey.js
This direction works to a point. We can output the passport session either on the screen or on the command line.
var passport = require('passport')
exports.launchLti = [
passport.authenticate('consumer', { session: false/true [tried both] }),
function(req, res) {
db.findByStudentUserId({lis_person_contact_email_primary:
req.body.lis_person_contact_email_primary}, function(err, user) {
req.logIn(user, function(err) {
req.user.username = user[0].lis_person_contact_email_primary;
...
// req.session.save(function(){
// res.redirect('/classes');
res.redirect(200,'/');
// });
});
})
// res.render('launch', {launch: launch});
}
}]
I solved this issue by changing some of my code structure.
app.pst('/launch/lti/:id', function(req, res, next) {
passport.authenticate('consumer', {failureRedirect: '/login'}),
dbConsumerKey.findByStudentUserId({},
function(err, user) {
if (err) console.log(err, user);
req.logIn(user, function(err) {
if (err) return err;
ADDED -> req.session.valid = true;
ADDED -> res.redirect('/');
});
}
});
});
and modifying the render page function to adapt to the incoming information.
function renderPage(req, res, pageName, pageTitle){
...
this is where the locals are created
...
this allowed me to use my current local strategy as is and adding a totally different strategy route but making the session correctly.

Passport.js authentication Failure

I tried to write a username/password authentication server based on this project:
https://github.com/tutsplus/passport-mongo.git
However I always receive a "Can\'t set headers after they are sent." error.
I don't want to use any login session so I removed all the code related to that.
Here is my code:
In app.js
......
// Configuring Passport
var passport = require('passport');
app.use(passport.initialize());
// Initialize Passport
var initPassport = require('./libs/auth/init');
initPassport(passport);
var routes = require('./routes/index')(passport);
app.use('/api', routes);
......
In ./libs/auth/init.js:
var signin = require('./signin');
var createuser = require('./createuser');
var User = require('../../models/user');
module.exports = function(passport) {
// Setting up Passport Strategies for Login and SignUp/Registration
signin(passport);
createuser(passport);
};
The signin.js:
var LocalStrategy = require('passport-local').Strategy;
var User = require('../../models/user');
var bCrypt = require('bcrypt-nodejs');
module.exports = function(passport) {
passport.use('signin', new LocalStrategy({
passReqToCallback : true
},
function(req, username, password, done) {
// check in mongo if a user with username exists or not
User.findOne({'username' : username},
function(err, user) {
// In case of any error, return using the done method
if (err) {
return done(err);
}
// Username does not exist, log the error and redirect back
if (!user) {
console.log('User Not Found with username ' + username);
return done(null, false);
}
// User exists but wrong password, log the error
if (!isValidPassword(user, password)){
console.log('Invalid Password');
return done(null, false); // redirect back to login page
}
// User and password both match, return user from done method
// which will be treated like success
return done(null, user);
}
);
})
);
var isValidPassword = function(user, password){
return bCrypt.compareSync(password, user.password);
}
};
,which is almost the same as the original project
Also the createuser.js is almost the same as the original project:
var LocalStrategy = require('passport-local').Strategy;
var User = require('../../models/user');
var bCrypt = require('bcrypt-nodejs');
module.exports = function(passport) {
passport.use('createuser', new LocalStrategy({
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, username, password, done) {
var findOrCreateUser = function() {
// find a user in Mongo with provided username
User.findOne({'username' : username}, function(err, user) {
// In case of any error, return using the done method
if (err) {
console.log('Error in SignUp: ' + err);
return done(err);
}
// already exists
if (user) {
console.log('User already exists with username: ' + username);
return done(null, false);
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.username = username;
newUser.password = createHash(password);
// save the user
newUser.save(function(err) {
if (err) {
console.log('Error in Saving user: ' + err);
throw err;
}
console.log('User Registration successful');
return done(null, newUser);
});
}
});
};
// Delay the execution of findOrCreateUser and execute the method
// in the next tick of the event loop
process.nextTick(findOrCreateUser);
})
);
// Generates hash using bCrypt
var createHash = function(password){
return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
}
};
The model file:
var mongoose = require('mongoose');
module.exports = mongoose.model('User',{
id: String,
username: String,
password: String
});
The ./routes/index.js is very different from the origin file. Because I am trying to implement user authentication apis, I want to send back some json data after user authentication instead of redirecting them to another url.
var express = require('express');
var router = express.Router();
module.exports = function(passport) {
router.post('/signin', function(req, res, next) {
passport.authenticate('signin', {session : false},
function(err, user, info) {
if (err) {
res.json({
message: "Internal Server Error!"
})
} else if (!user) {
res.json({
message: "No Such User!"
})
}
req.logIn(user, function(err) {
if (err) {
res.json({
message: "Login Failure!"
})
}
res.json({
message: "Login Success!"
})
});
})(req, res, next);
});
router.post('/createuser', function(req, res, next) {
passport.authenticate('createuser', {session : false},
function(err, user, info) {
if (err) {
res.json({
message: "Internal Server Error!"
})
} else if (!user) {
res.json({
message: "User Creation failure!"
})
}
res.json({
message: "Create User Success!"
})
})(req, res, next);
});
return router;
};
However this seems doesn't work well. For the signin api I receive that error message every time I make a request from curl, like:
curl --data "username=2232&password=223" http://localhost:3000/api/signin
For the createuser api only when create user succeeds it doesn't crash. Otherwise I will still receive that error message.
BTW, I am not sure what the done method is doing under the hood. Anyone can give me some details?
I would be appreciated if anyone can answer this question as well:
This is the first time I tried to design an web api. What I am trying to do seems odd to me: The server receives a username and password, then it looks it up in the database, if it finds it then just tell the client "hey I found you!". Then no side effect occurs.
I don't think this is the right way how authentication api works. I would expect the server generate some kind of access key together with an expiration time. However I don't find passport.js has the capacity to do that. Am I using the wrong lib to do the authentication api with node.js?
In your routes file you need to use return when sending the response, because just calling the res.json method the execution of function is not stopped and the server tries to send two responses, that's what the error says you.
You should modify your code:
router.post('/signin', function(req, res, next) {
passport.authenticate('signin', {session : false},
function(err, user, info) {
if (err) {
return res.json({
message: "Internal Server Error!"
})
} else if (!user) {
return res.json({
message: "No Such User!"
})
}
req.logIn(user, function(err) {
if (err) {
return res.json({
message: "Login Failure!"
})
}
return res.json({
message: "Login Success!"
})
});
})(req, res, next);
});
router.post('/createuser', function(req, res, next) {
passport.authenticate('createuser', {session : false},
function(err, user, info) {
if (err) {
return res.json({
message: "Internal Server Error!"
})
} else if (!user) {
return res.json({
message: "User Creation failure!"
})
}
return res.json({
message: "Create User Success!"
})
})(req, res, next);
});

Verify access/group in Passport.js

I would like to use passport.js to verify that when users hit certain endpoints that they not only have the correct password but are a member of a specific group or have a certain access.
For simplicity sake if I have access levels of USER and ADMIN.
I can use passport to authenticate a password:
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.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
Then with a route I can make sure the user passes auth:
app.get('/api/users',
passport.authenticate('local'),
function(req, res) {
res.json({ ... });
});
But lets say you need to have ADMIN acess to hit /api/users'. Do I need to write my own Strategies? IE do I need to have a local-user, and local-admin strategy, and in each verify the proper access levels?
I think I can do this pretty easily, but the problem arises when I need my site to have different auth methods (maybe sometimes use oauth), I would need to write custom *-user, *-admin strategies for each. Seems like overkill.
Other option is to just verify access/group in each route after the user has been authenticated. But I would prefer to do this in the middle-ware if possible.
Thanks
You could create a simple middleware that checks the group:
var needsGroup = function(group) {
return function(req, res, next) {
if (req.user && req.user.group === group)
next();
else
res.send(401, 'Unauthorized');
};
};
app.get('/api/users',
passport.authenticate('local'),
needsGroup('admin'),
function(req, res) {
...
});
This assumes that the object stored in req.user has a property group. This object is the one passed along from the strategy implementation and deserializeUser.
An alternative could be connect-roles, but I don't know how well that integrates with Passport.
EDIT: you could also combine Passport and the group-checking middleware:
var needsGroup = function(group) {
return [
passport.authenticate('local'),
function(req, res, next) {
if (req.user && req.user.group === group)
next();
else
res.send(401, 'Unauthorized');
}
];
};
app.get('/api/users', needsGroup('admin'), function(req, res) {
});

Resources