Comments (9)
Hi @vesse,
Thank you for the suggestion. I tried it and it worked!!! Here is my authentication code if anybody else needs it:
var jwt = require('jsonwebtoken');
var config = require('../../config');
var superSecret = config.secret;
var passport = require('passport');
var LdapStrategy = require('passport-ldapauth');
module.exports = function(app, express) {
var apiRouter = express.Router();
passport.use('ldap1', new LdapStrategy(config.ldap1));
passport.use('ldap2', new LdapStrategy(config.ldap2));
apiRouter.use(passport.initialize());
// route for authenticating users (POST /api/login)
apiRouter.post('/login', function (req, res, next) {
passport.authenticate(['ldap1', 'ldap2'], { session: false, userNotFound: 'Sorry, but we could not find that username.' }, function(err, user, info) {
// code here
})(req, res, next);
});
// other routes here
return apiRouter;
};
from passport-ldapauth.
Hi,
No, you can only pass one URL to ldapjs.
Maybe you could initialize two instances of passport-ldapauth
with different names and use both? Something like this (not tested):
passport.use('ldap1', new LdapStrategy(opts1, function(user, done){ });
passport.use('ldap2', new LdapStrategy(opts2, function(user, done){ });
app.post('/login', passport.authenticate(['ldap1', 'ldap2']), function(req, res) { });
from passport-ldapauth.
That's a good idea! I will try it. Thank you!
from passport-ldapauth.
Thank you this actually worked for me. I have a side question to ask. With this configuration, we always use both strategies correct? In other words, this doesn't behave as a failover in case the first strategy fails to authenticate and vice versa correct?
@mir4ef are you saving the user data at all? I'm trying to find an example to save returned attributes in a session store (I'm using redis) so they can be used later to authenticate certain pages / buttons for the user.
Thank you
from passport-ldapauth.
Hi @aHumanBeing,
Glad it helped somebody!
Yes, I believe it is using two strategies, but unfortunately it is not a failover due to an error handling problem in LdapAuth
(see #20). The application bombs out if the first server is not accessible, but works fine if the second, third, etc. are not. If we could capture this error, then it would be a failover (which is the idea of using more than ldap sever).
Yes, I store the user's information. I create a token using jwt and store any information there and pass it to the front end. The front end saves it in any way it wants i.e. localStorage
, etc. Then, when somebody comes to the app, I decode the token to check its validity. In your case, you can store, lets say, the department and check for the department before letting the user access specific route/api end points. Below is how I generate the token, store what I need and then verify the information before I let the user continue to the route. There are two ways (that immediately come to mind) to restrict access to specific routes based on user role.
Option one (based on your question probably this is not what you are looking for if you need to show/hide elements on the page based on a user's attribute and you can skip to option two :)) is to have one middleware before all of your protected routes. Then have another middleware before the role specific routes (demonstrated in the code below) that checks the user attribute before allowing the user to continue. Note, that the extra middleware I did as an example and haven't implemented it myself. The drawback of this implementation is that it is hard to handle more complex role based routing (however, a web search might return a solution, haven't checked myself, again that is from the top of my head right now).
Option two is to handle to role based routing in the front end (that's what I would do if your role based logic is really complex and you need certain elements on the page to show/hide based on a user's attribute - the second middleware is not needed in this case). I have an api end point which returns the decoded token information (just the user information that is stored in the token) to the front end and cache it or save it in sessionStorage
. I call that api end point only after successful authentication, so I have the user's information available and can do what I need to do - display the name in a greeting or, in your case, verify a user attribute to allow access to certain parts of the application, show/hide buttons.
Hope that helps! Let me know if you have any questions or something is not clear in my quick explanation :)
var jwt = require('jsonwebtoken');
var config = require('../../config');
var superSecret = config.secret;
var passport = require('passport');
var LdapStrategy = require('passport-ldapauth');
module.exports = function(app, express) {
var apiRouter = express.Router();
passport.use('ldap1', new LdapStrategy(config.ldap1));
passport.use('ldap2', new LdapStrategy(config.ldap2));
apiRouter.use(passport.initialize());
// route for authenticating users (POST /api/login)
apiRouter.post('/login', function (req, res, next) {
passport.authenticate(['ldap1', 'ldap2'], { session: false, userNotFound: 'Sorry, but we could not find that username.' }, function(err, user, info) {
var generateToken = function () {
var token = jwt.sign({
name: user.name,
username: user.mailNickname
department: user.departmentName,
location: user.locationDesk
// more user info
}, superSecret, {
expiresInMinutes: 1440 // 24 hours
});
// return the information including a token
return res.json({
success: true,
message: 'User ' + user.name + ' authenticated successfully!',
token: token
});
};
if (err)
return next(err);
if (!user) {
var message = 'Invalid credentials'; // default message
for (var data of info) {
if (!!data) {
message = data.message;
break;
}
}
return res.json({
success: false,
message: 'Authentication failed! ' + message + '.'
});
}
if (user) {
// check if you need to verify group membership (memberof), otherwise, just generate the token
if (!!config.ldapgroup) {
if (!!user.memberOf) {
for (var group of user.memberOf) {
if (config.ldapgroup === group)
return generateToken();
}
}
// if no group membership or no match was found, return no access message
return res.json({
success: false,
message: 'We are sorry, but it seems that user ' + user.name + ' doesn\'t have access to this application.'
});
}
return generateToken();
}
})(req, res, next);
});
// route middleware to verify a token
apiRouter.use(function (req, res, next) {
console.log('Somebody came to the app');
// check header, url parameters or post parameters for token
var token = req.body.token || req.params.token || req.headers['x-access-token'];
// decode token
if (token) {
// verify secret and check expiration
jwt.verify(token, superSecret, function (err, decoded) {
if (err) {
return res.status(403).send({success: false, message: 'Failed to authenticate token!'});
}
else {
// if everything is good, save the request to be used by other routes
req.decoded = decoded;
next(); // allow the users to continue to the other routes
}
});
}
else {
// if there is no token, return an HTTP response of 403 (forbidden) and an error message
return res.status(403).send({success: false, message: 'No token provided.'});
}
});
// routes any authenticated user can access here
// api endpoint to get user information
apiRouter.get('/me', function (req, res) {
res.send(req.decoded);
});
// route middleware to verify user can access the routes below
apiRouter.use(function (req, res, next) {
var userInfo = req.decoded
// verify department example
if (userInfo.department === config.departmentName)
next(); // allow the users to continue to the other routes
else
return res.status(403).send({success: false, message: 'User not allowed access to the section.'});
});
// user role protected routes here
return apiRouter;
};
from passport-ldapauth.
Thanks alot! Would you mind posting your config file (minus the secret info of course)? I'm curious about the search attributes but would like to view the file.
from passport-ldapauth.
Sure, not a problem! Here is my complete config file. It is pretty much the same as above, but with two sever configuration settings:
//config.js
var fs = require('fs');
module.exports = {
'port': process.env.PORT || 8080,
'env': process.env.ENV || 'develpment',
'secret': 'mysupersecret',
'ldap1': {
server: {
url: 'ldaps://server1.company.com',
bindDn: 'my-binding-username',
bindCredentials: 'my-binding-username-password',
searchBase: 'dc=company,dc=com',
searchFilter: '(sAMAccountName={{username}})',
tlsOptions: {
ca: [
fs.readFileSync(__dirname + '/certs/certificate-for-server1.cert')
]
}
}
},
'ldap2': {
server: {
url: 'ldaps://server2.company.com',
bindDn: 'my-binding-username',
bindCredentials: 'my-binding-username-password',
searchBase: 'dc=company,dc=com',
searchFilter: '(sAMAccountName={{username}})',
tlsOptions: {
ca: [
fs.readFileSync(__dirname + '/certs/certificate-for-server2.cert')
]
}
}
},
ldapgroup: 'group-to-compare-user-membership-in-verify-function'
};
I search only for the sAMAccount. In your case it might be different parameter(s). However, if you want to see all the attributes a user carries, you can put a console.log(user);
in the passport.authenticate
callback:
//api.js
//...
apiRouter.post('/login', function (req, res, next) {
passport.authenticate(['ldap1', 'ldap2'], { session: false, userNotFound: 'Sorry, but we could not find that username.' }, function(err, user, info) {
console.log('user attributes:', user);
// var generate token = ...
})(req, res, next);
}
//...
from passport-ldapauth.
Thank you! I believe I may have it working (not sure how since I've been scratching my head all day) now but I'm still confused about a few things :)
-
You have the following: location: user.locationDesk
My question is, how does locationDesk exist within the user field? Is this pulled from ldap and turned into a field you can use to set the token? I have a field for distinguishedName but when I tried to use it the app crashed. -
Do you have a logout / quit button within your app? I have one and it will clear the passport object within the session but it will not delete the session from redis. I tried destroying the session but it didn't work. The sessions within redis have been set to expire as an alternate solution.
-
I noticed you're not using serializeUser, deserializeUser. This has been the core of my frustration in the past few days because I don't have a User object (as with mongoDB) and I don't have an id field so I was trying to figure out ways to create my own. Do you have any experience with this?
Side note, I'm appreciating my first experience with this new framework. The community here is a major + so thanks for your help and if there's something I can do let me know.
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
//var passport = require('./auth.js');
var passport = require('passport');
var LdapStrategy = require('passport-ldapauth');
var ldapConfig = require('./ldapConfig');
var expressSession = require('express-session');
var flash = require('connect-flash');
var redis = require('redis');
var redisConfig = require('./redisClientConfig');
var redisClient = redis.createClient(redisConfig.port, redisConfig.host, redisConfig.options);
var RedisStore = require('connect-redis')(expressSession);
// configure routes
var routes = require('./routes/index');
var login = require('./routes/login');
var local = require('./routes/local');
var remote = require('./routes/remote');
var digma = require('./routes/digma');
var history = require('./routes/history');
var locations = require('./routes/locations');
var providers = require('./routes/providers');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// uncomment after placing your favicon in /public
app.use(favicon(__dirname + '/public/favicon(5).ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
RedisStore.client = redisClient;
app.use(expressSession({ secret: 'aawwoivwfeoijkreroijlkqjlkjwqef',
resave: 'false',
saveUninitialized: 'false',
cookie: { maxAge: 3600000 * 12 },
unset: 'destroy',
store: new RedisStore({ttl:86400}),
}));
app.use(flash());
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
passport.serializeUser(function(user, done) {
console.log('serializeUser ' + user);
done(null, user);
});
/*passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(null, user);
});
});*/
passport.deserializeUser(function(user, done){
console.log('deserializeUser ' + user);
done(null,user);
});
passport.use('ldap1', new LdapStrategy(ldapConfig.ldap1),
function(user, done) {
return done(null, user);
});
passport.use('ldap2', new LdapStrategy(ldapConfig.ldap2),
function(user, done) {
return done(null, user);
});
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
if (app.get('env') === 'production') {
sess.cookie.secure = true; // serve secure cookies
}
app.post('/login', passport.authenticate(['ldap1', 'ldap2'], {session: true,
failureRedirect:'login',
failureFlash: 'Invalid user name or password.'}), function(req, res) {
//console.log('req.session.redirect_to ' + req.session.redirect_to);
var redirect_to = req.session.redirect_to ? req.session.redirect_to : '/';
delete req.session.redirect_to;
res.redirect(redirect_to);
//res.redirect('/');
//res.send({status: 'ok'});
});
app.use('/', routes);
app.use('/login', login);
app.use('/local', local);
app.use('/remote', remote);
app.use('/digma', digma);
app.use('/history', history);
app.use('/locations', locations);
app.use('/providers', providers);
/*
app.post('/login', function(req, res){
res.json(req.body);
})*/
app.get('/logout', function(req, res){
//req.session.destroy();
req.logout();
res.redirect('/login');
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
module.exports = app;
from passport-ldapauth.
You are welcome! :) glad you have some progress!
-
That is weird that the DN crashes your application. It should be just a string, unless your AD is configured differently? Yes, locationDesk is a field in ldap. All the fields that I save in the token are coming from LDAP (in my case). However, you can add custom values to the token if you need to. Every AD is setup differently, so you might have fields that I don't have and vice versa.
-
Yes, I have a logout button, which clears the token (removes it from
localStorage
). You can remove it using plain JS like this:
localStorage.removeItem(token); // token is the key that you used to store the token value
I will be honest with you, I haven't worked with redis at all and I can't really come up with any ideas as to why you have problems with it. Sorry about that!
- I don't have any experience with that. I've been rolling with the token so far and express middleware to prevent users to go where they are not supposed to :) I pass the token with each request to the backend (node) and the middleware takes care of that for me. If there is no token or it cannot pass the validation in the middleware (expired, wrong secret, etc.), I return 403 (as you can see in my first reply to you). This is how I've been managing 'sessions' if you will.
You can take a look at this great post by Chris Sevilleja regarding the token based authentication and express middleware - https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens. He explains it better than I ever can :)
Hope that is of any help to you!
from passport-ldapauth.
Related Issues (20)
- How can I tell when receiving the message Unauthorized if it for the LDAP bind credentials or the username I am searching for? HOT 1
- How to add SameSite strict to passport-Idapauth session cookie?
- Remove @types from package.json "dependencies" and place them in "devDependencies" HOT 1
- How to use dynamic ldap config options in a Nestjs app? HOT 1
- can we use passport-ldapauth for react app authentication
- using dynamic bindDN & bindCredentials from POST query HOT 2
- `errorhandler` called twice in strategy.js if LDAP server unreachable HOT 1
- To find which credentials is not valid.
- STARTTLS for passport-ldapauth HOT 1
- Comma in firstname or lastname fails user authentication.
- Real Error should also been handled as failed if multiple url provided
- this.fail is not a function HOT 1
- LdapAuth and verifyCredentials
- Authentication not working if user cannot log on to domain server HOT 4
- Error when installing HOT 1
- Update to new issue template format
- Unable to attempt authenticate HOT 2
- passport-ldapauth does not allow caching of ldap responses by ldapauth-fork HOT 3
- got Unauthorized message but ldapsearch work HOT 2
- LDAP Search Fails Due To Spaces Inserted Into BaseDN HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from passport-ldapauth.