This application is a template for NodeJS authentication using LDAP(S) and, optionally, AngularJS or anything else. It is also HTTP/2
enabled.
The backend of the application (NodeJS) communicates with LDAP. If the authentication is successful, the backend will create a token and pass it to the frontend to store it and pass it back with each request.
For more information please see Settings below.
-
NodeJS is required (v >= 6.3.0). It can be downloaded and installed from here.
-
Bower is required to install the front-end dependencies. You can install
bower
by runningnpm install -g bower
. Note: The-g
flag will install it globally and requires admin (sudo
) rights for the current user. -
Gulp is used (in this template) to build the custom style sheets, javascript files, assets, documentation (you can replace it with any build tool you feel comfortable with (e.g.
grunt
,webpack
, etc.)). You can install it globally by runningnpm install -g gulp
. To build them, please rungulp build
to build all tasks. Note: The-g
flag will install it globally and requires admin (sudo
) rights for the current user. -
Karma is optional. If you want to run the unit tests, you can install it globally by running
npm install -g karma-cli
. To run them, please runkarma start
to havekarma
run the tests every time you make a change orkarma start --singleRun
to run it only once. Note: The-g
flag will install it globally and requires admin (sudo
) rights for the current user. -
Nodemon is optional. It is used to automatically restart/reload the server on changes to the back/front-end. It can be downloaded and installed by running
npm install -g nodemon
. Then, you can just run the server by going to the application folder and typingnodemon server.js
. Note: The-g
flag will install it globally and requires admin (sudo
) rights for the current user.
- you need the
build-essential
s installed on Ubuntu and RedHat - clone the application (you need
git
installed to do it) - you (might) need to install
libpng
on RedHat ((sudo) yum install libpng-develm
, if that one doesn't work, you can try(sudo) yum install libpng-devel
) - install all backend dependencies, including the development ones, by running
npm install
from the application folder. Note that after successful installation, this will automatically trigger thepostinstall
script frompackages.json
, which will install all frontend dependencies, build the custom stylesheets, javascript, ect., and run the karma tests once - if, for whatever reason,
npm install
didn't trigger thepostinstall
script, you can install all frontend dependencies by runningbower install
from the application folder. Note that if do not have write rights for the application directory, you might need to execute it withsudo
and--allow-root
option (sudo bower install --allow-root
), because it needs to make and write to a new directory (the one specified in.bowerrc
. the.bowerrc
file specifies the destination of the frontend dependencies) - if, for whatever reason,
npm install
didn't trigger thepostinstall
script, you can build all the assets by runningnpm run build
from the application folder
-
you will need to generate certificates and place them in the certs folder (
appname/server/certs
) for local development. if you don't know how to generate.pem
and.key
files, you can search the internet or read this post or run this in your terminalopenssl req -x509 -newkey rsa:4096 -keyout appname.key -out appname.pem -days 365
(please use 2048 encryption and above when generating the certs, e.g.rsa:2048
). If you setup a password for your certs, you will need to provide it when you start the server with the environmental variableCERTPHRASE
-
you will need to specify a
secret
to encrypt and decrypt the tokens. you can do this by setting the environmental variableSECRET
when you start the server -
you will need to specify an LDAP user and password to connect and query LDAP to verify users. to do this, you will need to set
LDAPUSER
andLDAPPASS
environmental variables when you start the server. Note that the app is already configured to useLDAPS
-
if you want to verify against a specific LDAP group membership, you will need to pass a group name, enclosed in quotes, to the
LDAPGROUP
environmental variable when you start the server (e.g.LDAPGROUP='cn=my ldap group,ou=groups,dc=com'
). Note that if you do not pass a group, it will just verify the user against LDAP, but no group membership -
if you use the default port configuration, the url will be
https://localhost:8080
. if you are using a different port (by setting the environmental variablePORT
when you start the server), update the URL accordingly. Note thathttp
won't open anything, ONLYhttps
is allowed! -
when you start the server, your final startup command should look something like this:
CERTPHRASE='myphrase' SECRET='mysupersecret' LDAPUSER=user.name LDAPPASS='password-for-user.name' node server.js`
or if using nodemon (recommended)
CERTPHRASE='myphrase' SECRET='mysupersecret' LDAPUSER=user.name LDAPPASS='password-for-user.name' nodemon server.js
Configuration settings to match your application needs/specifications:
-
The LDAP(S) properties are defined in the
config.js
file:1.1 Regular LDAP (non-secure):
//config.js //... 'secret': 'superSecret', // a password/phrase used to encode/decode the token 'ldap': { server: { url: 'ldap://servername.company.com', bindDn: 'cn=service.account,ou=System Accounts,dc=company,dc=com', bindCredentials: 'password', searchBase: 'dc=company,dc=com', searchFilter: '(sAMAccountName={{username}})' } }, ldapgroup: 'CN=Group_Name,OU=Company Groups,DC=company,DC=com' // optional, need it if user group membership is required to access the application //...
To see all the available options for the server settings, please see here.
1.2 Secure LDAP (= LDAPS): You will need to prefix the ldap url with
ldaps
and add the certificate(s) under thetlsOptions
:const fs = require('fs'); //... 'ldaps': { server: { url: 'ldaps://servername.company.com', bindDn: 'cn=service.account,ou=System Accounts,dc=company,dc=com', bindCredentials: 'password', searchBase: 'dc=company,dc=com', searchFilter: '(sAMAccountName={{username}})', tlsOptions: { ca: [ fs.readFileSync(__dirname + '/app/certs/certificateName.pem') ] } } }, //...
Ideally, you should use only one LDAP(S) URL - the load balancer, and not worrying about one LDAP sever going down, etc. That is the job of the load balancer, not yours. BUT, if, for whatever reason, you need to use more than one LDAP URL, you can:
1.3 Using multiple LDAP URLs:
- Each URL requires separate set of settings. For example, to specify two LDAP URLs, you will need to add a second set of server options/settings to your
config.js
:
//config.js const 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.pem') ] } } }, '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.pem') ] } } }, ldapgroup: 'group-to-compare-user-membership-in-verify-function' };
- You will also need to initialize a new
LdapStrategy
for each LDAP URL and pass the option for each one. In addition, you will have to pass all the strategies to thepassport.authenticate()
method:
//api.js const jwt = require('jsonwebtoken'); const config = require('../../config'); const superSecret = config.secret; const passport = require('passport'); const LdapStrategy = require('passport-ldapauth'); //... passport.use('ldap1', new LdapStrategy(config.ldap1)); passport.use('ldap2', new LdapStrategy(config.ldap2)); apiRouter.use(passport.initialize()); apiRouter.post('/login', authUser); function authUser(req, res, next) { passport.authenticate(['ldap1', 'ldap2'], {session: false}, function(err, user, info) { // login logic })(req, res, next); } //...
If you use more than one strategy, hence, more than one URL, your 'failed to authenticate' response message will be an array of objects (rather than just an object):
//... passport.authenticate(['ldap1', 'ldap2'], {session: false}, function(err, user, info) { //... if (!user) { console.log((Object.prototype.toString.call(info) === '[object Array]'); // true //... } //... })(req, res, next); //...
Note: Using more than one LDAP URL should work as a failover. However, currently it is not due to an error handling problem in
LdapAuth
(see this post). The application bombs out if the first server is not accessible, but works fine if the second, third, etc. are not. - Each URL requires separate set of settings. For example, to specify two LDAP URLs, you will need to add a second set of server options/settings to your
-
The content and the duration of the token can be customized in the application in the
api.js
file under the/authenticate
api endpoint:const generateToken = function () { const token = jwt.sign({ name: user.name, username: user.mailNickname }, superSecret, { expiresInMinutes: 1440 // 24 hours }); // return the information including a token as JSON return res.json({ success: true, message: 'User ' + user.name + ' authenticated successfully!', token: token }); };
-
API documentation is auto generated (with
gulp
) using Swagger UI and is accessible underappurl/documentation/api
-
This template uses AngularJS and its documentation is auto generated (with
gulp
) using angular-jsdoc and is accessible underappurl/documentation/app