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.
Related
I'm trying to get the user to be able to sign up to my website, and store the credentials on mongodb.
This is my auth.js file, where the route is defined:
router.post('/signup', (req,res,next) => {
Passport.authenticate('local-signup', err => {
if (err) {
if (err.name === "MongoError" && err.code === 11000) {
res.status(409).json({
success: false,
message: "Unsuccessful",
errors: {
email: "This email is already taken."
}
});
}
res.status(400).json({
success: false,
message: "Unsuccessful",
errors: {
unknown: "Could not process for some reason. Contact admin."
}
});
}
res.status(200).json({
success: true,
message: "Successful",
errors: {}
});
}) (res, req, next);
}
That last bracket got a bit messed up but believe me, it's not a syntax error.
This snippet is where I have defined the passport strategy:
require ('../Models/Users')
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/onlinestore');
const User = mongoose.model('User');
const PassportLocalStrategy = require('passport-local').Strategy;
const Passport = require('passport');
module.exports = Passport.use(new PassportLocalStrategy({
usernameField: 'email',
passwordField: 'password',
session: false,
passReqToCallback: true,
}, (email, password, done) => {
let user = new User();
user.email = email.trim();
user.password = password.trim();
user.save(err => {
if (err) {
console.log(err);
return done(err);
} else {
console.log("Success");
return done(null);
}
});
}
));
The route is able to get the user inputted password and user. When I click submit, literally nothing happens; the server doesn't console anything nor does the client. After a bit of debugging I think the issue is due to the fact that Passport.Authenticate is not being called but I'm not sure why. I can post other code snippets if necessary, thanks!
This is OP. I was able to find the solution.
1) In my auth.js file,
Passport.authenticate('local-signup', err => {
replace the 'local-signup' with 'local'
so it becomes:
Passport.authenticate('local', err => {
2) I happened to have multiple Passport strategies and each are in their own file, (one for login and one for signup). To use the correct "local" I have to import the correct one in my auth.js file. Easy enough, it's
const passport = require('../Passport/local-signup');
For login, it would be a different file.
I'm implementing a custom callback in PassportJS, however the additional info is not being passed through to the callback.
The documentation states:
An optional info argument will be passed, containing additional
details provided by the strategy's verify callback.
Such that if I were to pass the following to the callback:
return done(null, false, {message: 'Authentication failed.'});
with this demo code...
app.get('/login', function(req, res, next) {
passport.authenticate('basic', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.status(401).json({message: info.message}); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.status(200).json({message: 'success'});
});
})(req, res, next);
});
The info variable should contain a message field with the above mentioned value, however the info variable only contains the following:
"Basic realm=\"Users\""
I've looked at countless examples but I cannot figure out why the additional info is not being attached to the info variable. Any ideas?
I am using this code to add details . You may have to use flash message (connect-flash) library to send flash message and read them .
See if this fits your requirement
passport.use(
new LocalStrategy(
{},
function(username, password, done) {
var promise =authenticateService.authenticateUser(username,password);
promise.then(function(authToken)
{
if(authToken=="INVALID CREDENTIALS")
{
return done(null, false,{});
}else{
return done(null, {
username: username,authToken:authToken
},{"sucess":"sucesss"});
}
})
})
);
I'm running into an issue with my index.js file trying to call isAuthenticate() using passportjs with expressjs. When I make the function call req.isAuthenticated() never gets called and will always return false.
Brief explanation, I'm trying to authenticate the user so they can access pages that they are authorized to view.
Here is a link to the repo https://github.com/SpotOnSoftwareInc/web-app
note: this was code that I inherited from someone else and a lot of these tools are new to me.
'module.exports = function (passport) {
router.get('/login', login.get);
router.post('/login',passport.authenticate('local-login',{
//session: false,
successRedirect : '/registerprocess',
failureRedirect : '/register',
failureFlash: true
}));
function isLoggedInBusAdmin(req, res, next) {
//if user is authenticated in the session, carry on
if (req.isAuthenticated() && ((req.user[0].role === 'busAdmin') || (req.user[0].role === 'saasAdmin'))){
return next();
}
req.flash("permission", "You do not have permission to access that page");
// if they aren't redirect them to the home page
res.redirect('back');
}'
lib\auth.js
`function isAuthenticated(req, res, next) {
console.log('ENTERED IS AUTHENTICATED');
isValidToken(req.db, req.headers.authorization, function (result) {
if (!result) {
console.log('Not Authenticated');
return res.send(403);
} else {
req.mobileToken = result[0];
console.log('Authenticated PASS');
return next();
}
});
}
}`
Found the error in my app.js file
instead of calling
employee.find({_id: id}, function (err, user){
i was calling
employee.findById({_id: id}, function (err, user){
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);