Git Product home page Git Product logo

Comments (9)

mir4ef avatar mir4ef commented on August 17, 2024 3

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.

vesse avatar vesse commented on August 17, 2024

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.

mir4ef avatar mir4ef commented on August 17, 2024

That's a good idea! I will try it. Thank you!

from passport-ldapauth.

aHumanBeing avatar aHumanBeing commented on August 17, 2024

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.

mir4ef avatar mir4ef commented on August 17, 2024

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.

aHumanBeing avatar aHumanBeing commented on August 17, 2024

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.

mir4ef avatar mir4ef commented on August 17, 2024

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.

aHumanBeing avatar aHumanBeing commented on August 17, 2024

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 :)

  1. 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.

  2. 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.

  3. 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.

mir4ef avatar mir4ef commented on August 17, 2024

You are welcome! :) glad you have some progress!

  1. 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.

  2. 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!

  1. 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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.