Related
I'm building an express js api with passport js, and in order to be able to return custom error messsages formatted as json I'm using custom callbacks.
When I provide an unknown email the custom callback I wrote is called 3 times, resulting in Unhandled rejection Error: Can't set headers after they are sent.. Which makes sense.
Any help is appreciated.
Here is my implementation:
Strategy:
const localLoginStrategy = new LocalStrategy({
usernameField: "emailAddress"
}, (emailAddress, password, done) => {
// Called once
User.findOne({
where: { emailAddress }
}).then((existingUser) => {
// Called once
if (!existingUser) { return done(null, false, { message: "Invalid email/password combination", status: 401 }); }
return existingUser.comparePassword(password);
}).then((userData) => {
return done(null, userData);
}).catch((err) => {
return done(null, false, { message: "Invalid email/password combination", status: 401 });
});
});
passport.use(localLoginStrategy);
Express middleware for authentication using custom callback:
const requireUsernamePassword = (req, res, next) => {
if(!req.body.emailAddress || !req.body.password) {
return res.status(400).json({ message: "No emailAddress and/or password provided" });
}
// Called once
passport.authenticate("local", { session: false }, (err, user, info) => {
// Called three times!
console.log("authenticate callback")
if (!user || err) {
return res
.status(info.status || 400)
.json({ message: info.message || "Authentication error" });
}
req.user = user;
return next();
})(req, res, next);
};
To check your mandatory request body fields create one generic middleware that will check required field and return appropriate return code. Just like below.
module.exports = function checkParams(params) {
params = params || [];
return function(req, res, next) {
var valid = true;
if(Array.isArray(params)) {
params.forEach(function(_param) {
valid = valid && !!req.body[_param];
});
}
if (valid) { next() } else {return res.status(400).end();} //this is for missing required parameters
};
};
Now lets say for example you have two APIs. Login and CreateUser. API routes should looks like below
app.post('/Login', checkParams(['emailAddress', 'password']), passport.authenticate('local', { failureRedirect: '/login' }), actualLoginMethod);
app.post('/CreateUser', checkParams(['userName', 'Phone']), passport.authenticate('local', { failureRedirect: '/login' }), actualCreateUserMethod);
If either of these parameter (userName and Phone in /CreateUser + emailAddress and password in /Login) is missing then it will return 400 status and stop execution from that point, you may change the checkParams's logic as per your need.
If required parameters are available then it will check JWT local strategy. Once request is through to both the check points then it will call actual method.
Hope this might help you.
You are calling the done function multiple times.
I believe when you call return done(...) in the then method, the next then will call done again.
So that's why your callback function from requireUsernamePassword has been called more then one time.
Hope it helps.
I am trying to set up a simple authentification system with Parse Server:
app.js
...
app.get('/login', (req, res) => {
res.render('login.ejs');
});
app.post('/login', (req, res) => {
console.log('POST /login\t' + util.inspect(req.body));
driver.login(req, (err, user) => {
//Here, user is defined
if(err) {
res.redirect('/login');
} else {
res.redirect('/user');
}
});
});
...
driver.js:
...
function login(req, callback) {
var username = req.body.username,
password = req.body.password;
Parse.User.logIn(username, password, {
success: (user) => {
callback();
},
error: (user, error) => {
callback(JSON.stringify(error));
}
});
}
function isLoggedIn(req, callback) {
console.log('isLoggedIn?');
console.log(util.inspect(req.user)); //undefined
if(req.user) {
callback();
} else {
callback('Not logged in');
}
}
...
When I access /login, I can login just fine, and get redirected to /user without any error, but on /user, which use isLoggedIn as a middleware, req.user is undefined.
I have seen others with the same problem when searching, but the post where either old (<2015), using another part of the JSSDK (react/browser), or just didn t get any answer.
I know I could use session, and recreate the user each time based on that, but it feels really hackish, is it really the supported way?
You have two routes to go, either have a REST-full server, which means users are not persistent between route calls, STATE-full and use sessions.
Luckily there is a really good nodejs authentication middleware already build that will handle all this session managment. This is called passportJS. Documentation can be found here: http://passportjs.org/docs
You can not only have authentication through local logins, but have support for authentication with google, facebook, github and many more. This is done through what are called Strategies. You use google-strategy for having google oauth, facebook-stradegy for facebook oauth, etc.
What you would be looking for is a local-strategy, which is called this because you want to authenticate with local user credentials. This strategy can be found here: https://www.npmjs.com/package/passport-local
you will need both passport and passport local and to install simply run
npm install passport passport-local
From there, just go through the documentation I have linked above for how to set everything up.
To develop my answer to blablabla comment, who asked what I ended up using.
In my case, I was develloping a REST API, and the session data wasn't expected to change once connected.
So what I did is delegate the session to the client, and go full stateless, by using JWT.
When the client is authentified, I encrypt his session data in a Json Web Token, and send him. When he try to access a protected page, he send me the JWT, which I can decrypt, and recreate req.user based on the information within.
My middleware look like this:
function isLoggedIn(req, res, next) {
//If there is a token
if(req.headers != null && req.headers.token != null) {
//Verify it and extract the user's data
verify(req.headers.token, (err, user) => {
if(err != null) {
res.status(401);
res.json({error: err});
} else {
//Recreate req.user
req.user = user;
next();
}
});
} else {
res.status(401);
res.json({error: 'No token'});
}
}
function verify(token, callback) {
//Verify the token
jwt.verify(token, jwtSecret, (error, user) => {
if(error != null) {
callback(error);
} else {
Separately check if logged into Parse
parse.isLoggedIn(user, (error, loggedIn) => {
if(error != null) {
callback(error);
} else if(!loggedIn) {
callback('Not logged in the Parse');
} else {
callback(null, user);
}
});
}
});
}
I am currently trying to set up an admin role in order to access a simple admin page using the following documentation provided via : connect-roles
I ave been banging my head against it for a while and am still lost on how to set a role E.G As of right now am pulling a admin value out of the DB and storing it in a global var for the time being but I have no idea how to use that with connect-roles say to only allow access to my admin page for a specific user.
Can anyone clarify or show an example on how to do this/some guidance as I documentation didn't help me to ensure access to a web page only if the user is an admin?
Ave posted some of the code kinda showing what it looks like at the moment.
Code
var admin = 'Admin';
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '',
database : 'test'
});
var passport = require('passport');
var ConnectRoles = require('connect-roles');
var roles = new ConnectRoles();
var passportLocal = require('passport-local');
app.use(passport.initialize());
app.use(passport.session());
app.use(roles.middleware());
passport.use(new passportLocal.Strategy(function (username, password, done) {
connection.query({
sql : 'SELECT * from `userman_users` WHERE `username`= ?AND`password` = sha1(?)',
timeout : 40000, // 40s
values : [username, password]
}, function (error, results, rows) {
if (results.length > 0) {
response = "Success";
} else {
console.log('Error while performing Query.');
response = "Failed";
}
if (response === "Success") {
done(null, {
id : username
});
} else if (response === "Failed") {
done(null, null);
}
});
})
);
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
done(null, {
id : id
});
});
roles.use(function (req, action) {
if (!req.isAuthenticated()) return action === 'access home page';
})
roles.use(function (req) {
if (req.user.role === 'admin') {
return true;
}
});
app.get('/', redirectToIndexIfLoggedIn, function (req, res) {
res.render('login');
});
app.get('/index', checkLoggedIn, function (req, res) {
res.render('index', {
isAuthenticated : req.isAuthenticated(),
user : req.user
});
});
app.get('/admin', user.can('access admin page'), function (req, res) {
res.render('admin');
});
function checkLoggedIn(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect('/');
}
this is an example:
var express = require('express');
...
var passport = require('passport');
var LocalStrategy = require('passport-local');
var ConnectRoles = require('connect-roles');
...
var app = express();
//===============PASSPORT=================
// Passport session setup.
passport.serializeUser(function(user, done) {
console.log("serializing " + user.username);
done(null, user);
});
passport.deserializeUser(function(obj, done) {
console.log("deserializing " + obj);
// simulate an admin user
obj.role = obj.username == 'admin' ? 'admin' : 'user';
done(null, obj);
});
...
//===============CONNECTION RULES=================
var user = new ConnectRoles({
failureHandler: function (req, res, action) {
// optional function to customise code that runs when
// user fails authorisation
var accept = req.headers.accept || '';
res.status(403);
if (~accept.indexOf('html')) {
res.render('access-denied', {action: action});
} else {
res.send('Access Denied - You don\'t have permission to: ' + action);
}
}
});
...
app.use(passport.initialize());
app.use(passport.session());
app.use(user.middleware());
//anonymous users can only access the home page
//returning false stops any more rules from being
//considered
user.use(function (req, action) {
if (!req.isAuthenticated()) return action === 'access home page';
});
//users logged can access to public pages
user.use(function(req, action){
if(req.isAuthenticated() && action != 'access private page' && action != 'access admin page')
return true;
});
//moderator users can access private page, but
//they might not be the only ones so we don't return
//false if the user isn't a moderator
user.use('access private page', function (req) {
console.log('access private page');
if (req.user.role === 'moderator') {
return true;
}
});
//admin users can access all pages
user.use(function (req) {
if (req.user.role === 'admin') {
return true;
}
});
...
/* GET home page. */
app.get('/', user.can('access home page'), function(req, res, next) {
res.render('index', { title: 'Express' });
});
//displays our signup page
app.get('/signin', function(req, res){
res.render('signin');
});
//sends the request through our local signup strategy, and if successful takes user to homepage, otherwise returns then to signin page
app.post('/local-reg', passport.authenticate('local-signup', {
successRedirect: '/',
failureRedirect: '/signin'
})
);
//sends the request through our local login/signin strategy, and if successful takes user to homepage, otherwise returns then to signin page
app.post('/login', passport.authenticate('local-signin', {
successRedirect: '/',
failureRedirect: '/signin'
})
);
// Simple route middleware to ensure user is authenticated.
app.use(function(req, res, next) {
if (req.isAuthenticated()) { return next(); }
req.session.error = 'Please sign in!';
res.redirect('/signin');
});
//logs user out of site, deleting them from the session, and returns to homepage
app.get('/logout', function(req, res){
var name = req.user.username;
console.log("LOGGIN OUT " + req.user.username)
req.logout();
res.redirect('/');
req.session.notice = "You have successfully been logged out " + name + "!";
});
app.get('/private', user.can('access private page'), function (req, res) {
res.render('private');
});
app.get('/admin', user.can('access admin page'), function (req, res) {
res.render('admin');
});
app.use('/users', users);
....
module.exports = app;
With connect-rules you define the rules do you want to use (user.use in this case). If you pass an action as first parameter the strategy is only used if the action passed in the function is equal to it. Then you trigger the rules in the routes with user.can passing the action. In this example I define an extra filter strategy to grant access to users that are logged and request routes that are not marked with admin or moderator privileges e.g
/* GET home page. */
app.get('/', user.can('access home page'), function(req, res, next) {
res.render('index', { title: 'Express' });
});
After the user is logged, we need to have another strategy in case the user isn't admin or moderator.
U can use framework like sailsJS and npm module sails-generate-auth
And after setup, use your own middleware to block routes
//allow admin only localhost:PORT/admin at policies.js
'admin': ['passport', 'sessionAuth', 'isAdmin'],
'*': ['passport', 'sessionAuth'],
//isAdmin policy
module.exports = function(req, res, next) {
// User is allowed, proceed to the next policy,
// or if this is the last policy, the controller
if (req.user.role == 'admin') {
return next();
}
// User is not allowed
return res.forbidden('You are not permitted to perform this action.');
};
Using the following logic I was able to have admin functionality based on value within the DB:
app.get('/admin', function (req, res) {
connection.query({
sql : 'SELECT role from `auth_users` WHERE `username`= ?',
timeout : 40000, // 40s
values : [req.user['id']]
}, function (error, results, rows) {
if (results[0]['role'] === "admin") {
admin = (results[0]['role']);
res.render('admin', {
isAuthenticated : req.isAuthenticated(),
user : req.user
});
} else {
admin = "";
res.redirect('/index');
}
})
});
The below custom call back for passport.js doesn't seems to work, no mater what i do.
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, users, info) {
console.log(users);
if (user === false) {
console.log('Failed!');
} else {
res.redirect('/');
}
})(req, res, next);
});
The same if i change it to like below all works as expected.
app.post("/login"
,passport.authenticate('local',{
successRedirect : "/",
failureRedirect : "/login",
})
);
Also I've noticed when using custom callback even the passport.serializeUser and passport.deserializeUser also not getting invoked by passport.js.
Is this any sort of a bug or am i doing something wrong here ??
My Local-Strategy:
passport.use('local-sigin',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
},
function(req, email, password, done) { // callback with email and password from our form
console.log('Passport Strategy Sign in:');
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'email' : email }, function(err, user) {
// if there are any errors, return the error before anything else
if (err)
return done({status:'ERROR',message:'Something went wrong!'});
// if no user is found, return the message
if (!user)
return done({status:'ERROR',message:'No user found.'}, false);
// if the user is found but the password is wrong
if (!user.validPassword(password))
return done({status:'ERROR',message:'Oops! Wrong password.'}, false);
// all is well, return successful user
return done({status:'OK',message:'Login success.'}, user);
});
}));
I am guessing that by 'doesn't work' you mean to say that the user is never being logged in.
Firstly, your local strategy is named 'local-sigin' however on a POST to '/login' you are invoking the 'local' strategy, which presumably doesn't exist:
passport.use('local', new LocalStrategy({
Change the name of your strategy to be consistent (or vice versa!):
passport.authenticate('local'
Secondly, your 'local' authentication callback has a parameter users (plural) but you are trying to access user (singular) within its body, meaning user is undefined and user === false is false under strict equality:
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
// ^^^^
console.log(user);
if (!user) {
console.log('Failed!');
} else {
res.redirect('/');
}
})(req, res, next);
});
And finally, you are never logging the user in when authentication is successful. Creating a session for a user is not automatic, you must call req#login:
Passport exposes a login() function on req (also aliased as logIn()) that can be used to establish a login session.
Let's add that to your authentication callback:
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
console.log(user);
if (!user) {
console.log('Failed!');
} else {
req.login(user, function (err) {
if(err) {
console.log(err);
return;
}
res.redirect('/');
});
}
})(req, res, next);
});
Take a look at the Passport docs, they explain in a good amount of detail how these processes work and how to implement them.
When configuring passport using Express and NodeJS, I am throwing an error if the user has an invalid email address. After this error I would like to redirect to a failure page giving them instructions on how to log in correctly. Is there are a better way to do this? If not, how would I go about catching the error in some fashion and redirecting to a new page.
passport.use(new GoogleStrategy({
clientID : auth.googleAuth.clientID,
/* Settings redacted for brevity */
},
function(token, refreshToken, profile, done) {
User.findOne(
{
"google.id" : profile.id
},
function(err, user) {
if (err) return done(err)
if (user) return done(null, user)
else {
if (email.indexOf("lsmsa.edu") > -1) {
// Code redacted for brevity
} else {
done(new Error("Invalid email address"))
}
}
}
)
}))
I think you can use this:
Redirects
A redirect is commonly issued after authenticating a request.
app.post('/login',
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login' }));
In this case, the redirect options override the default behavior. Upon
successful authentication, the user will be redirected to the home
page. If authentication fails, the user will be redirected back to the
login page for another attempt.
Or this:
Custom Callback
If the built-in options are not sufficient for handling an
authentication request, a custom callback can be provided to allow the
application to handle success or failure.
app.get('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
Please read the document:
https://www.passportjs.org/concepts/authentication/downloads/html/#middleware
Note: I quite like BlackMamba's answer as well, adding the custom callback / redirect is a perfectly acceptable option.
Simply add your own error handling middleware to Express:
passport.use(new GoogleStrategy({
clientID : auth.googleAuth.clientID,
/* Settings redacted for brevity */
},
function(token, refreshToken, profile, done) {
User.findOne({
"google.id" : profile.id
},
function(err, user) {
if (err) return done(err)
if (user) return done(null, user)
else {
if (email.indexOf("lsmsa.edu") > -1) {
} else {
// Throw a new error with identifier:
done(throw {
type: "invalid_email_address",
code: 401,
profileId: profile.id
}));
}
}
}
)
}));
// The error handling middleware:
app.use(function(e, req, res, next) {
if (e.type === "invalid_email_address") {
res.status(e.code).json({
error: {
msg: "Unauthorized access",
user: e.profileId
}
});
}
});
You'll notice I modified this answer a little bit with a more robust error composition. I've defined the code property of the error to match the applicable HTTP status code -- in this case, 401:
// callback
done(throw {
// just a custom object with whatever properties you want/need
type: "invalid_email_address",
code: 401,
profileId: profile.id
}));
In the error handling, we simply check the type is invalid_email_address (you can make it whatever you want, but it should be consistent across your app) and then write the error out using the "code" as the HTTP status code:
// e is the error object, and code is the custom property we defined
res.status(e.code).json({
error: {
msg: "Unauthorized access",
user: e.profileId
}
});
Here's a self-contained working example with a redirect:
var express = require('express');
var app = express();
app.all('*', function(req, res) {
throw {type: "unauthorized", code: 401}
})
app.use(function(e, req, res, next) {
console.log(e);
if (e.code === 401) {
res.redirect("/login")
} else {
res.status(500).json({error: e.type});
}
});
app.listen(9000);