Authentication with passport-ldapauth nodejs module in express - node.js

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

Related

Passport-ldapauth fails to execute verify callback

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.

Google Oauth giving code redeemed error

Hi i am working on a project where a user logs in via google account.(localhost)
I have implemented the google signup.
As soon as I log in from my account I am getting the below error.
TokenError: Code was already redeemed.
at Strategy.OAuth2Strategy.parseErrorResponse (c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\lib\strategy.js:298:12)
at Strategy.OAuth2Strategy._createOAuthError (c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\lib\strategy.js:345:16)
at c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\lib\strategy.js:171:43
at c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:176:18
at passBackControl (c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:123:9)
at IncomingMessage.<anonymous> (c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:142:7)
at IncomingMessage.emit (events.js:129:20)
at _stream_readable.js:908:16
at process._tickCallback (node.js:355:11)
My code is as follows(snippet for google login):-
passport.use(new GoogleStrategy(google, function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) {
console.log('There is already a Google+ account that belongs to you. Sign in with that account or delete it, then link it with your current account.' );
done(err);
} else {
User.findById(req.user.id, function(err, user) {
user.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
user.profile.displayName = user.profile.displayName || profile.displayName;
user.profile.gender = user.profile.gender || profile._json.gender;
//user.profile.picture = user.profile.picture || 'https://graph.facebook.com/' + profile.id + '/picture?type=large';
user.save(function(err) {
console.log('Google account has been linked.');
done(err, user);
});
});
}
});
} else {
User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
if (existingEmailUser) {
console.log('There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' );
done(err);
} else {
var user = new User();
user.email = profile._json.email;
user.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
user.profile.displayName = profile.displayName;
user.profile.gender = profile._json.gender;
//user.profile.picture = 'https://graph.facebook.com/' + profile.id + '/picture?type=large';
user.profile.location = (profile._json.location) ? profile._json.location.name : '';
user.save(function(err) {
done(err, user);
});
}
});
});
}
}));
I am stuck on it.Please help me out..thanks
The problem is not in your "snippet", look at the routes. It should be absolute path on redirect for google.
router.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '#/signIn' }),
function(req, res) {
// absolute path
res.redirect('http://localhost:8888/#/home');
});
It's known issue, follow this link to other workarounds
https://github.com/jaredhanson/passport-google-oauth/issues/82
I have come across this issue. The exact problem is your route.
app.get('/auth/google/callback', passport.authenticate('google'), (req, res) => {
res.send('get the data');
});
At this point app had got user permission and google send a code to this url. Now what passport does here it took that code and made a request to google for user details and got it from google. Now we have to do something with this details otherwise you will get the error that you have got.
Now we can use serialiseUser and deserialiseUser of passport to save details in cookie and edit one line of above code to go at some url like that.
app.get('/auth/google/callback', passport.authenticate('google'), (req, res) => {
res.redirect('/servey'); // just a url to go somewhere
});
I also had the same problem since few days. What I figured out is, you just need to complete the process. Until now you have only checked whether the user is present in the database or not. If not then you save the user to the database.
However, after this, when the google tries to redirect the user, the code that google+ API sent is already used or say it is no longer available. So when you check the user in your database, you need to serialize the user i.e store the code into your browser in a cookie so that when google redirects the user, it know who the user is. This can be done by adding the code given below.
//add this in current snippet
passport.serializeUser(function(user,done){
done(null,user.id);
});
To use this cookie, you need to deserialize the user. To deserialize, use the code given below.
//add this in current snippet
passport.deserializeUser(function(id,done){
User.findById(id).then(function(user){
done(null, user);
});
});
Also, you are required to start a cookie session and you can do this by adding the below code in your main app.js file.
const cookieSession = require('cookie-session');
app.use(cookieSession({
maxAge: 24*60*60*1000, // age of cookie, the value is always given in milliseconds
keys:[keys.session.cookiekey]
}));
//initialize passport
app.use(passport.initialize());
app.use(passport.session());
Note that you need to require the cookie-session package. Install it using
npm install cookie-session
Also, you require to write absolute URI in the callbackURL property in your google strategy.
I had the same problem.
Reseting client secret from google console solved the problem.

LDAP authentication using passport-ldapauth npm

I am trying to authenticate openLDAP username and password using passport-ldapauth npm. While executing the below code I am always getting error as
{ message: 'Missing credentials' }. Kindly help me what is wrong with my code.
var connect = require('connect'),
app = connect(),
passport = require('passport'),
LdapStrategy = require('passport-ldapauth');
// Credentials from the free LDAP test server by forumsys
// More info at: http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
var OPTS = {
server: {
url: 'ldap://<ip>',
bindDn: '<admin username>',
bindCredentials: '<admin password>',
usernameField: "<passing actual username>",
passwordField: "<password>"
}
};
passport.use(new LdapStrategy(OPTS));
app.use(passport.initialize());
app.use(connectRoute(function (router) {
router.post('/login', function (req, res, next) {
passport.authenticate('ldapauth', {session: false}, function (err, user, info) {
console.log(info);
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (!user) {
return res.send({success: false, message: 'authentication failed'});
}
return res.send({success: true, message: 'authentication succeeded'});
})(req, res, next);
});
}))
app.listen(8080);
For more details, please see this badRequestMessage flash message for missing username/password (default: 'Missing credentials')
Here is my configuration:
var passport = require('passport');
var LdapStrategy = require('passport-ldapauth').Strategy;
var OPTS = {
server: {
url: '<ldap server>',
bindDn: '<admin username>',
bindCredentials: '<admin password>',
searchBase: '<base dn>',
searchFilter: '(sAMAccountName={{username}})'
}
};
passport.use(new LdapStrategy(OPTS));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
The usernameField and passwordField in OPTS are optional. My program is using the default value username and password. If the usernameField is set, you need to modify searchFilter as (sAMAccountName={{<usernameField value>}}) or (uid={{<usernameField value>}}).
Also, in order to store the login status in your session, express-session module is required. The session configuration is like:
var session = require('express-session');
app.use(session({
secret: 'ldap secret',
resave: false,
saveUninitialized: true,
cookie : { httpOnly: true, maxAge: 2419200000 } /// maxAge in milliseconds
}));
Then, you can use the LDAP authentication as:
app.post('/login', passport.authenticate('ldapauth', {
successRedirect: '/users/profile', failureRedirect: '/login'
}));
According to the documentation provided by passport-ldapauth, the object which is the value of server does not contain the usernameField and passwordField. Your strategy should look like:
var OPTS = {
server: {
url: 'ldap://<ip>',
bindDn: '<admin username>',
bindCredentials: '<admin password>'
},
usernameField: "<field containing username>",
passwordField: "<field containing password>"
};
But as G Chen mentions in his answer, the usernameField and passwordField are optional.
Please check the following Code where Basic Authentication of the user has to be performed. This code works when a user login credentials have to be verified.
We need to use 3 fields viz. usernameField, passwordField and credentialsLookup
`var basicAuth = require('basic-auth');
var OPTS = {
server: {
url: Constants.LDAP_SERVER_URL_STRING,
bindDn: Constants.LDAP_ADMIN_STRING,
bindCredentials: Constants.LDAP_PASSWORD_STRING,
// searchBase: Constants.LDAP_SEARCHBASE_STRING,
// searchFilter: Constants.LDAP_SEARCHFILTER_STRING
// reconnect: true
},
usernameField: username,
passwordField: password,
credentialsLookup: basicAuth
};
LDAP Administration tools like Userbooster light are very useful in understanding how the authentication process happens.
Fields such as searchBase, searchFilter are of no use. The disclaimer however is that you need to test to see if it is true
var basicAuth = require('basic-auth');
var LdapAuth = require('ldapauth-fork');
var username: string = req.body.username;
var password: string = req.body.password;
var ldap = new LdapAuth({
url: Constants.LDAP_SERVER_URL_STRING,
bindDN: Constants.LDAP_BIND_DN_STRING,
bindCredentials: Constants.LDAP_PASSWORD_STRING,
searchBase: 'uid=' + username + ',' + Constants.LDAP_SEARCHBASE_STRING,
searchFilter: Constants.LDAP_SEARCHFILTER_STRING
// reconnect: true
});
ldap.authenticate(username, password, function(err, user) {
if (err) {
console.log("login Error");
res.send({ success : false, message : 'authentication failed' });
} else if(!user.uid) {
console.log("user not found Error");
res.send({ success : false, message : 'authentication failed' });
} else if(user.uid) {
console.log("success : user "+ user.uid +" found ");
}
});
Maybe the issue is not caused by the passport-ldapauth. There may be some problems with your post requests. Check if there are [usernameField], [passwordField] in your request before using passport.authenticate.
Please check NPM with "LDAP Authentication"
You will come across a package called ldapauth-fork. This package seems to be working correctly.
Please check the following link https://www.npmjs.com/package/ldapauth-fork
Don't use admin username or password in DN. If you want to authenticate a user, all you need is the user's own username and password.
The dn is the Bind DN in LDAP. Depends on your ldap server configuration, it varies. Use ldapsearch to experiment to find out which one should you use.
I wrote an npm module based on passport-ldapauth to simplify the ldap authentication login.Please check it out at: https://github.com/shaozi/express-passport-ldap-mongoose
Simple usage:
LdapAuth.init(CONFIG.ldap.dn, CONFIG.ldap.url, app,
(id) => User.findOne({ uid: id }).exec(),
(user) => User.findOneAndUpdate({ uid: user.uid }, user, { upsert: true, new: true }).exec()
)
A Change in ldapauth-fork/lib/ldapauth.js bindProperty from dn to upn :
this.opts.bindProperty || (this.opts.bindProperty = 'userPrincipalName')
causes username authentication.
Full dn is not needed.
After wasting lot of time, I finally able to fix it. Some my findings
{{username}} replacement happens ONLY ON searchFilter. I was doing it on searchBase
Make sure your request body has username and password filed and you have used correct body-parser otherwise passport will notable to extract
As passport was not showing any error it was failing silently, add debugger in two places in the of the library
In ldapauth.js search for LdapAuth.prototype.authenticate here you will able to see ldapauth is able to extract password/username
In strategy.js search for ldap.authenticate here you will be able to see what is the actuall error

Express post /signup 500 unknown error with passport

I have a nodeJS application using express and passport. Im trying to handle the post requests to passport for a login. Im getting an unknown error in my server console: POST /signup 500
First question, how do I debug this? is there any way for me to get more information about why its 500'ing?
Second question, any idea's why its not working from a quick glance of what I have below. I know its not all the code, but maybe there is something obvious..?
app.js:
..
var passport = require('passport');
require('./config/passport')(passport);
..
var routes = require('./routes/index')(app, passport);
..
Passport.js defines my handling of users and the database. It defines a localStragery called local-signup.
/routes/index.js:
module.exports = function(app, passport) {
..
var express = require('express');
var router = express.Router();
..
router.post('/signup',
passport.authenticate('local-signup', {
successRedirect : '/profile',
failureRedirect : '/signup' })
);
..
return router
}
Update:
My code is actually working, it is putting new users in the database, its just the application is returning 500. Using loads of console.log() and trial and error I have tied it down to what i returning from my passport stratagy. I have the following code wich is being hit:
..
} else {
console.log("-] Email is not found, creating new user");
user = {};
user['email'] = email;
user['password'] = bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
console.log("-] Inserting into db");
userCollection.insert( user, function(err, user) {
if (err) {
console.log("Error: " + err );
console.log("records: " + user );
throw err;
}
console.log("returned user: " + user)
return done(null, user);
its something to do with me returning user. If I get rid of this line and just return instead, it works fine but the call to authenticate thinks it has failed and follow that's redirect rather than the one for success. The issue is that i got this code from an application that makes use of mongoose which wraps the database stuff in an object. I don't want to do that, i want to make the db calls directly myself like the code above.
Any idea's?
You're not making use of the error handling in the API.
Try something like this instead:
passport.authenticate('local-signup', {
successRedirect: '/profile',
failureRedirect: '/login',
failureFlash: true })
... this tells passport to 'flash' any error resulting from the callback (assuming there's one defined as part of the verify stage of local-signup).
Now, assuming local-signup in your application is set up like so:
var local-signup = require('passport-local').Strategy;
... you should be able to sort the error handling pretty easily. Here's a sample from the configuration docs at http://passportjs.org/guide/configure/ -- note how errors are returned:
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);
});
}
));
For me the solution was to not use:
passport.authenticate('local-signup', {
But:
passport.authenticate('local', {
This is the default if you do not set a name when registering your strategy like so:
passport.use(new LocalStrategy({
You can pass a name in if you have multiple LocalStrategies:
passport.use('local-signup', new LocalStrategy({

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.

Resources