Passport-ldapauth fails to execute verify callback - node.js

Please , I have setup passport ldapauth which works fine with all parameters, the problem is if the username or password is wrong, the it does not execute further to the verify callback function at all. It just stops. Due to this I cannot give feedback to the users to indicate what is actually wrong. Is there any clue what I am missing?. This is the structure
passport.use('ldapStudent', new LdapStrategy({
usernameField: 'username',
passReqToCallback:true,
server: {
url: '..........',
bindDn: '.............',
bindCredentials: '..........',
searchBase: '..............',
searchFilter: '.............',
searchAttributes: ['givenName','sn'],
tlsOptions: {
ca: [fs.readFileSync('./ssl/server.crt', 'utf8')]
}
}
},
function (req, user, done) {
//now check from the DB if user exist
if(user){
//check if user email exist;
User.findOne({'EmailAddress': user}, function (err, userdata) {
// In case of any error, return using the done method
if (err)
return done(err);
//user exist redirect to home page and send user object to session
if (userdata) {
//userActivity(PostActivity);
console.log(userdata);
return done(null, userdata);
}else {
//new user, add them to the user model
var newUser = new User();
newUser.EmailAddress = req.body.username,
newUser.JoinedDate = Date.now(),
newUser.UserType = 'Student'
newUser.save(function (err, result) {
if (err) {
console.log('Error in Saving NewUser: ' + err);
} else {
console.log(result);
var PostActivity = {
ActivityName: req.res.__('Student Joined'),
ActivityDate: Date.now(),
UserID: result._id,
UserIP: (req.header('x-forwarded-for') || req.connection.remoteAddress ) + ' Port: ' + req.connection.remotePort
};
userActivity(PostActivity);
console.log('User Registration successful');
return done(null, newUser, req.flash('SuccessMessage', req.res.__('You have been successfully Registered')));
}
})
}
});
}else{
return done(null, false, req.flash('ValidationError', req.res.__('Wrong password and/or email address')));
}}));
This is where i actually do the login
router.post('/login', passport.authenticate('ldapStudent', {
successRedirect: '/',
failureRedirect: '/userlogin',
failureFlash: true
}));
The code works well , just as I expect, the parameters for the ldap option object are intentionally omitted.
The problem is when the user credential are not correct, the verify callback does not get executed at all and so, I can not return a flash message for the user to know what is happening

passport-ldapauth does not execute the verify callback if there is nothing to verify which is the case if the credentials are incorrect and the user is not received. This is in general how the strategies tend to work, e.g. passport-local does not execute verify callback if the username or password is missing.
Strategies, passport-ldapauth included, also usually include a (configurable) message for the failure flash. General configurable login failure messages for passport-ldapauth are listed in the documentation. Each of the messages also has a default value so even when not configured the failure flash message is set (given of course that you have flash middleware in use)
Also, you are not supposed to use req.flash() in the callback of the verify function but to supply an info message.

Related

Passport fails authentication with "Wrong password" before actually calling my passwordMatch function

This is a weird one.
What im trying to do
Create an authentication server in node.js using Passportjs local strategy, and JWT. Allowing for account registration with email & password, with passwords hashed with 'crypto'
What's happening
So when I login with the right password, to a pre existing model, authentication fails in the APi for having a wrong password. Though theres some weird stuff going on.
What I've tried
Essentially when I make the post request:
OPTIONS /api/login calls
It goes through my passport config, and in the typical function where you check if the password is correct
side note: POST api/login is logged to the console
The function in my passport config:
if (!profileController.passMatch(username, password)) {
console.log('pass was wrong');
return done(null, false, {
message: 'Password is wrong'
});
}
The 'pass was wrong' thing calls, failing the authentication with done(). Though in passMatch, as you'll see below, it does show the correct password
passMatch function in profile controller:
module.exports.passMatch = (email, password) => {
User.findOne({email: email}, (err, user) => {
if (err) { console.log ("error at passMatch: " + err); }
var hash = crypto.pbkdf2Sync(password, user.salt, 1000, 64, 'sha512').toString('hex');
console.log(user.hash == hash);
return (user.hash == hash);
});
return false;
};
Though if you notice the console log where I check if the hash comparison is correct. That log statement is printed to the console after 'pass was wrong' is logged. It's also printed after the passport.authenticate call in my login function concludes a failed authentication at the console.log(info)
Login function:
module.exports.login = (req, res) => {
console.log('beginning to authenticate');
passport.authenticate('local', (err, user, info) => {
console.log ("authenticating");
var token;
// If passport throws an error
if (err) {
res.status(404).json(err);
console.log("error logging in");
return;
}
// If a user is found
if (user) {
// Respond with JWT
token = createJwt(user)
res.status(200);
res.json({
"token": token
})
console.log("user logged in");
// If a user wasn't found
} else {
res.status(401).json(info);
console.log(info);
}
})(req, res);
};
Error logging in isn't called, but the console.log(info) is called with the error message from the done() message in the config.
What's going wrong here?
In the "passMatch" function, I query for the user again (which is just inefficient), but since this operation was asynch, it was being skipped to the "return false" statement after, and in the passport authentication config process, it recieved that false, causing authentication to fail, but the "log" to be returned after cause it took longer.
How I fixed it
I passed in the user object that passport already queried instead of the username into passMatch, then had two operations to check if the hash was the same and returned that, and now it works.
The new code
module.exports.passMatch = (user, password) => {
var hash = crypto.pbkdf2Sync(password, user.salt, 1000, 64, 'sha512').toString('hex');
return user.hash == hash;
};
Also the necessary change in the passport config to pass in the user instead of the username as the first param to that function.

Authentication with passport-ldapauth nodejs module in express

I'm having issues authentication authenticate an user for an ldap server. I'm pasting the important code below. Can anyone tell me what are the missing pieces of this or whether this nodejs module is able to do what I want to do? I've been trying this for two days now and couldn't find a solution.
const passport = require('passport');
const ldapStrategy = require('passport-ldapauth').Startegy;
var LDAP_OPTS = {
usernameField: 'myAccountUsername',
passwordField: 'myPassword',
server: {
url: 'ldap://xx.xx.x.xx:389',
bindDN: 'dn related data',
bindCredentials: 'adminAccountPassword',
searchBase: 'dc=xyz,dc=com',
searchFilter: '(sAmAccountName={{myUserNameForTheAccount}})'
},
};
passport.use(new ldapStrategy(LDAP_OPTS));
app.use(passport.initialize());
module.exports.login = function(req, res, next){
passport.authenticate('ldapauth', function(err, user, info){
console.log('inside authent '+JSON.stringify(info))
if(err){
return next(err);
}
if(!user){
res.status(401).json({ success: false, message: 'authentication failed' })
// res.send({success: false})
console.log('inside user: '+user)
} else {
res.status(200).json({success: true})
// res.send({success: true})
}
console.log('after response..')
})(req, res, next)
When I run this I get a {"message":"Missing credentials"}. I googled for many resources but haven't found a proper one. There are examples using "passport-local" but I couldn't find "passport-ldapauth" example that has authenticating an user with his username & password. Even in this exapmle, we just sent a request to ldap server to check the existence of an user but I don't know how to validate his password after returning the information of that user.
The github page of passport-ldapauth links to ldapauth-fork project. According to the documentation it says that you need to add the property bindProperty to your configuration object.
ldapauth-fork says that bindProperty is
/**
* Property of the LDAP user object to use when binding to verify
* the password. E.g. name, email. Default: dn
*/
bindProperty?: string;
Hope that can help

Authenticate user with passport through LinkedIn login

I have built a login system in Passport and works quite well. Now, I want to integrate LinkedIn login in my system. I already have clientID, clientSecret etc. needed to login. This is the code that is called when the LinkedIn login button is pressed.
passport.use('linkedin', new OAuth2Strategy({
authorizationURL: 'https://www.linkedin.com/uas/oauth2/authorization',
tokenURL: 'https://www.linkedin.com/uas/oauth2/accessToken',
clientID: clientid,
clientSecret: clientsecret,
callbackURL: '/linkedinLogin/linkedinCallbackUrlLogin',
passReqToCallback: true
},
function(req,accessToken, refreshToken, profile, done) {
console.log('authenticated');
console.log(accessToken);
req.session.code = accessToken;
process.nextTick(function () {
done(null, {
code : req.code
});
});
}));
Both the console.log() calls in the callback function are successfully fired, this means I am successfully logged in through LinkedIn and I receive my access token. The part where I connect with LinkedIn is thus correct, what I am missing is the part where I actually log in the user. As you can see, the callbackURL points to /linkedinLogin/linkedinCallbackUrlLogin. This is what I do in that route:
app.get('/linkedinLogin/linkedinCallbackUrlLogin', passport.authenticate('linkedin', {
session: false,
successRedirect:'/linkedinLogin/success',
failureRedirect:'/linkedinLogin/fail'
}));
I just specify a successRedirect and a failureRedirect. Note that if I put session : true I receive as an error Failed to serialize user into session, so for now I keep it to false.
The successRedirect is successfully called. In that route I call a GET request to LinkedIn to access some data about the user. I want to store this data in my DB and remember the user that logged in. This is how I do it:
https.get(
{
host: 'api.linkedin.com' ,
path: '/v1/people/~?format=json' ,
port:443 ,
headers : {'Authorization': ' Bearer ' + req.session.code}
},
function(myres) {
myres.on("data", function(chunk) {
var linkedinJsonResult = JSON.parse(chunk);
User.findOne({linkedinLogin : linkedinJsonResult.id}, function(err, userSearchResult){
if(err) {
throw err;
}
//user found, login
if(userSearchResult){
console.log(userSearchResult);
}
else {
//create user
var newUser = new User(
{
url : linkedinJsonResult.siteStandardProfileRequest.url,
name : linkedinJsonResult.firstName + " " + linkedinJsonResult.lastName,
linkedinLogin : linkedinJsonResult.id,
regDate : new Date()
}
);
//save user
newUser.save(function(err, user){
if(err){
throw err;
}
//login
console.log(user);
});
}
});
});
}
);
Let me explain the code there. After getting the data of the user I check the field "id" that is received. If this id matches one of my users' linkedinLogin field stored into the DB, I consider it already registered (the user has been found in the DB), thus I have to log him/her in. Otherwise I just create a new user using the data received from the GET request.
My question is, in both the cases - the user is found in my DB, or the user has to be created - how can I set req.user to be my user whenever it interacts with my website? Is it sufficient to just do req.user = userSearchResult (if the user is found, inside the if statement) or req.user = user (if the user has been created, inside the newUser.save() callback), or should I call some passport functions that will set it for me?
All the other passport functions related to the registration and login of users without using LinkedIn login are working fine. I am just worried about making this LinkedIn login work with passport.
Thank you.
passport.js will automatically set the req.user object to the object you will pass as the second argument to the done function of the strategy callback.
This means that you should do something like this:
function(req,accessToken, refreshToken, profile, done) {
console.log('authenticated');
console.log(accessToken);
req.session.code = accessToken;
process.nextTick(function () {
// retrieve your user here
getOrCreateUser(profile, function(err, user){
if(err) return done(err);
done(null, user);
})
});
}));
I hope this helps.

Access to "req" Object in Supertest After a Response

Is there any way to directly access the req object in supertest, while/after the request is being tested? I want to test my passport strategies, so I want to check req.user, req.session, and perhaps others. I know I can test page redirects or flash, as those are what my strategies do, but it seems useful to see if there is a user on the req object, as well. If I do this, I can also check how many users there are at any one time.
I will sign users up with the "local-signup" strategy, which is defined thusly:
'use strict';
// get passport & mongoose
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var UserModel = require('mongoose').model('User');
module.exports = function() {
// signup function
passport.use('local-signup', new LocalStrategy({
passReqToCallback: true // pass the entire request to the callback
},
function(req, username, password, done) {
process.nextTick(function() {
// find a user with the same username
UserModel.findOne({username: username}, function(err, user) {
// if there is an error, log it then return it
if(err) {
console.log("Error finding a user in the database: " + err);
return done(err);
}
// if a user was already found
if(user) {
return done(null, false, "User already exists");
}
// if we get this far, create a new user from the request body
var newUser = new UserModel(req.body);
// save it and sign it in
newUser.save(function(err) {
if(err) {
console.log("Error during signup: " + err);
return done(err);
}
return done(null, newUser);
});
});
});
}
));
};
One way I use this strategy is like this:
My "local" strategy is defined like this:
'use strict';
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var UserModel = require('mongoose').model('User');
module.exports = function() {
// create our local passport strategy & use it
passport.use(new LocalStrategy({
// use the default names for the username & password fields
usernameField: 'username',
passwordField: 'password'
},
// main strategy function
function(username, password, done) {
// find user with given username
UserModel.findOne({
username: username
},
// with this username, do this
function(err, user) {
// if there's an error, log it then pass it along
if(err) {
console.log("Error during login: " + err);
return done(err);
}
// if the username and/or password is incorrect, return an error
// along with a message
if(!user || !user.authenticate(password)) {
return done(null, false, {
message: 'Invalid username and/or password'
});
}
// if everything is correct, return the user document from the database
return done(null, user);
});
}
));
};
I use both strategies like this, for example:
app.route(pageName).post(function(req, res, next) {
passport.authenticate(strategyName, function(err, user, info) {
if(err || !user) {
res.status(401).send(info);
}
else {
req.login(user, function(err) {
if(err) {
res.status(400).send(err);
}
else {
res.send(null);
}
});
}
})(req, res, next);
});
I tried
request = require('supertest');
this.authServer = require('../my-server');
request(this.authServer)
.put('/signup')
.set('Content-Type', 'application/json')
.set('Host', 'konneka.org')
.send(this.fullUser)
.end(function(req, res, done) {
console.log(res);
});
The res object I logged, inside the end() function, which was way too long to show here, has a req object defined on it, but it seems to only have the objects & functions that were defined before the request was opened. In other words, it does not have req.user, req.session, or other objects I want, because they are defined after the request completes and a new request is started. I noticed it has status codes, as well, which are only defined after the request completes, so I must be missing something.
Is there any way to get access to the req object after the request you are testing is ended? Or am I going about this completely the wrong way?
You cannot do what you want using supertest.
Not sure if this helps but I'll add a little context to clarify the answer:
supertest is a wrapper on top of superagent (client side) with some basic hooks into express to start up the HTTP listener. Under the hood, it really is not any different from starting up your express app, waiting for it to listen on a port, making an HTTP request to that port, and parsing the result. In fact, that is exactly what it does.
So essentially supertest only has access to what ever your client would have access to (a browser or some API client). In other words, if it isnt in the HTTP response body, you wont have access to it. req.user and req.sesssion are server side state variables that are (most likely) not in the response (unless you are doing something strange).
If you want to test in exactly the way you describe, you will have to use some alternative strategy of testing, not supertest.
I found this question when I thought I wanted to do this, and for me it worked well to check the status of the user created by the request instead of verifying the content of the req object. You do have access to the full database where I assume you users somehow ends up.

Express.js/Passport app creating new session for each request, despite session ID in request headers

This has not been noticed before, because for client-server communications, nothing requiring authentication is happening via XHR (as of yet). In implementing some integration tests, I am creating realy requests node-xmlhttprequest to a real instance of the application. User is being authenticated in the first request, and in theory the user's unique identifier and some other pertinent information is stored in session (as I've said, this works just fine for real clients, who do not need to confirm their identity over XHR). For some reason, even though subsequent requests are being fired with the exact same session ID, I am not able to retrieve the session in subsequent requests, and thus can't see that the user is authenticated, and this leads to failing tests where the expected behaviour does not happen, and HTTP 401 UNAUTHORIZED is filling up the terminal.
I've seen a couple of questions which look sort of like this, but that "same session ID" thing does not seem to be present among them.
Do I need to manually add Set-Cookie headers to all XHR requests? That's terrible, there's got to be a better way!
So people like source code, here's some from the test, firing these requests:
// Ensure logged in before sending request, to verify that authorized
// users encounter the expected behaviour.
jQuery.post(domain+'/login', {email:'test#express.app', password:'12345678'},
function(data,x,y){
data.should.equal("true")
jQuery.ajax({url:domain+'/event', type:"POST",
name: "TestEvent", location: "TestVille",
startDate: new Date('2013-09-01'), startTime: '6:45 pm',
description: "Test Event #1", success: state.success,
error: state.error, completed: state.completed
})
})
Sign in happens, user ID gets written into session (`req.logIn() should do this, and it does not report any failures), serialization of the user ID does not report any failures, and I am extremely confused as to why subsequent requests using the same session ID are unable to find the serialized user ID.
I am generally not involved with web development, so this may be obvious to some people, but I've been searching for an answer all day and have simply not been able to find one. I'd appreciate any pointers, and am happy to provide as much code as I'm able to, to illustrate what the problem is.
A few additional points of code which may be pertinent:
Serialization/deserialization of user ID (currently implemented in the simplest possible
manner -- This is very early in the initiallization of middleware, after initiallizing passport and passpot.session(). And this works perfectly well for non-XHR requests)
// Serialize user for passport session-store
// Currently only serializing 'user_id'
passport.serializeUser(function(user, done) {
done(null, user._id)
})
// Deserialize user from session-store to provide
// access to the User instance
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user)
})
})
Authentication of users via the local strategy:
passport.use(new LocalStrategy({
usernameField: "email", passwordField: "password",
passReqToCallback: true, failureFlash: true
},
function(req, email, password, done) {
User.findByEmail(email, function(err, user) {
if(user) {
if(err) console.log(err)
if(user instanceof UserLocal) {
user.verifyPassword(password, function(err, match) {
if(err) console.log(err)
if(!match) {
return done(err, false,
"Username or password is incorrect.")
}
return done(null, user)
})
} else {
var msg = "Account registered under " + user.providerName()
+ ", please login using " + user.providerName()
return done(err, false, msg)
}
} else {
return done(err, false, "Username or password is incorrect.")
}
})
})
And finally the requests which write to session in the first place:
function loginHelper(req, res, next) {
passport.authenticate('local', {
failureFlash:true,
failureRedirect: false,
successRedirect: false
},
function(err, user, info) {
req.logIn(user, function(err) {
if(!err) err = {}
if(req.xhr) {
console.log(req.session)
res.status(200).end(err.message || "true")
} else {
if(err.message) req.flash('error', err.message)
else res.redirect('/')
}
})
})(req, res, next)
}
I know there are some weird things like sending a status of 200 regardless of the login status, but I can confirm that the serialized user id is written to session on the initial login XHR request, and that it's not deserialized on subsequent XHR requests.
As I believe I mentioned, I am relatively inexperienced in this area, and could very much use a boost. Any assistance whatsoever would be much appreciated, thanks in advance.
That's help me
First
app.use(express.static(__dirname + '/public', { maxAge: oneDay }));
and update functions to do not trigger database
passport.serializeUser( (user, done) => {
var sessionUser = { id: user.dataValues.id, fio: user.dataValues.fio, email: user.dataValues.localemail, role: user.dataValues.role, login: user.dataValues.login, position: user.dataValues.position }
done(null, sessionUser)
})
passport.deserializeUser( (sessionUser, done) => {
// The sessionUser object is different from the user mongoose collection
// it's actually req.session.passport.user and comes from the session collection
done(null, sessionUser)
})
https://www.airpair.com/express/posts/expressjs-and-passportjs-sessions-deep-dive

Resources