Authenticate against LDAP in Node Red

In my application flow, I want to authenticate a user in an on-premise LDAP.

I have tried several contrib-nodes and passport and can't get it to work.

I have been able to successfully authenticate using node-red-contrib-nr-ldapauth and node-red-node-ldap, but both require a bind user, which is not configurable on each authentication, because it's a config node. I don't have a bind user from AD, and don't want to use my personal account, so would want to use the user credentials I am trying to authenticate, but it's not configurable.

I tried passport, and ldapauth-fork, and can't seem to make it work. All examples use express which I don't think will work within Node Red.

I already have a login page and and an entire application written in Node Red, but I can't get the authentication working.

Can anyone help me with real working examples in Node Red function nodes?

Thanks in advance.

If all you need to do is prove that the given username and password are correct, then you don't always need a binding username and pwd in order to do a search. If you are not interested in the user's groups or name or other LDAP properties, you can just bind to the LDAP server using the user/pwd of the user you want to authenticate.

e.g. binding to LDAP://domainController1.acme.local:389 with a user/pwd will tell you if it is valid or not. This actually binds to the 'rootDSE' - the root of the directory and should always be readable by an authenticated user.

If the user/pwd is incorrect, you'll get an error. We do this using Java from Linux in a Microsoft Active Directory environment. Note that I have only tested it with Active Directory, but it should work for other LDAP providers.

Out of curiosity, I created a function by extracting the relevant bits from node-red-node-ldap which uses ldapjs to connect.

Here is the simple function...

const LDAP = global.get('ldapjs');

// The LDAP server or AD Domain Controller
// Would need to add additional options here to support LDAPs and cert validation etc
//
var client = LDAP.createClient({ url: 'LDAP://dc1.acme.local:389' });


// IMPORTANT - NEVER ALLOW A BLANK PASSWORD to be used as some LDAP providers will 
// not return an error, but bind anonymously, returning 'success' which would
// be wrongly interpreted as the user having authenticated.
//
var username = "domain\\username";
var password = "pwd";
if(password.length === 0)
{
    msg.payload = "Authentication failed - blank password";
    return msg;
}

// Bind to the rootDSE to test the user's credentials...
//
client.bind(username, password, function(err) 
{ 
    if (err)
    {
	    node.warn("Auth failed:" + err);
	    msg.payload = "Authentication failed";
	}
	else
	{
	   msg.payload = "Authenticated";
	}
	
    node.send(msg);
    
});

Pre-reqs - you need to install ldapjs and add it as a module in your node-red settings.js. See the info here on how to add external modules.

Don't forget to install ldapjs (npm install ldapjs) from the right path (~/.node-red).

Here is what the settings.js file looks like for me:

functionGlobalContext: { ldapjs:require("ldapjs") },

You'd need to make it bulletproof and modify it use TLS and parse input messages, change output messages etc.

FYI, the other option is to modify an existing node to take the incoming 'user to authenticate' as the binding user, but this may not always work - it depends on the permissions that the authenticating user has within your LDAP directory, which is why a lot of these libraries ask for a 'binding service account' up-front.

Be interested to know if this works for you and what LDAP provider you have (AD?)

Regards...

3 Likes

It really makes me frustrated when other people make it look so easy. I'm not sure what I was doing wrong, or maybe I don't care anymore. I tried your code and it works beautifully and it's so simple. Thanks much!

1 Like

Node-RED uses Express internally so it is certainly possible to use it. How you use it depends on what you are trying to authenticate.

Thanks, but with the solution provided by @stew, I don't need to use Express at all.

Glad it helped, I might make a node for it if I ever get bored :slight_smile:

FYI, the LDAP stuff was easy when you know, but it took me the best part of an hour to figure out that I had to use node.send(msg) within the callback function instead of return msg at the end of the function. New to Javascript/Node.js and had no idea that the callback was asynchronous and that the function would run to its end without waiting!

Do you know if this async-ness is a Node.js behaviour or Javascript?

1 Like

I wrestled with that too. As I understand it, it's because the function is being called to handle the result of the client.bind or client.search, and not run inline with the sequential js. This is the only instance in my code so far that needed node.send.

Follow up question?

Using openSSL -showcerts in the Node Red docker container, I connect to a DNS name, let's say ldap.com:389, and I can bind (note no ldap://). This also works if I connect to ldap.com:636.

In my Node Red code, using ldapjs, I use ldap://ldap.com:389 and it works fine.

However, if I change the Node Red code to use ldaps://ldap.com:636 it times out. Out of desperation, I also tried ldap.com:636 (and get an error that I need the ldap:// or ldaps:// part). Neither ldaps:// nor ldap:// work.

Any suggestions?

Hi.

That is strange. Just tried from Windows 10 to an AD DC with LDAPS on port 636 and it works without changing any of the code, just using LDAPS://dc.local:636 in the URL.

However, this is a domain-joined machine with all the trusted cert chains installed locally.

Does it work for you with LDAPS using node-red-contrib-ldap (this uses ldapjs)? If it does, then you may need to modify the function to take a TLS cert that will be used to verify your LDAP server's identity and/or change other TLS options in ldapjs.

For example, it could be that the underlying Node.js TLS component is trying to verify the cert used by your LDAP server and times out for some reason.

Regards,
Stu

Asymc is an important part of JavaScript and Node.js is "just" server-side JavaScript. JavaScript basically operates on an implicit loop which enables async-like behaviour on a single thread. Wherever you see something labeled as a "callback" or "promise", you know that you are dealing with async behaviour.

1 Like

Stu, can you provide a link to the node-red-contrib-ldap node you're referring to? I can't find this exact one, but lots of similarly named ones.

It is the node-red-node-ldap node that you said you had got working in the first post - it is called node-red-contrib-ldap on Github.

https://flows.nodered.org/node/node-red-node-ldap

It might be the AD I'm connecting to, but I cannot use that node without a bind ID, so it works if I add my ID/pwd to the config node, but it doesn't accept a mustache template so I can bind with the logged in user's ID/pwd

Yes, that node requires a bind ID, the purpose of trying it was to see if it will authenticate using LDAPS for you.

  • Does it work with LDAPS? If so, how did you configure it? e.g. did you provide a cert?
  • What is the specific error coming back from the ldapjs function when you try LDAPS?

Something else to try as I came across a timeout issue - see if this updated function changes anything (added TLS option, a reconnect directive and a configurable timeout)...

 const LDAP = global.get('ldapjs');

// The LDAP server or AD Domain Controller
// Would need to add additional options here to support LDAPs and cert validation etc
//
var tlsOptions = {
    host: 'dc1.acme.local',
    port: '636',
    // Path your CA cert if you need one
   //  ca: [fs.readFileSync('./path/to/cert.pem')]
};

var client = LDAP.createClient({ url: 'LDAPS://dc1.acme.local:636',
                                    reconnect: true,
                                    tlsOptions: tlsOptions,
                                    ConnectTimeout: 10000 });


// IMPORTANT - NEVER ALLOW A BLANK PASSWORD to be used as some LDAP providers will 
// not return an error, but bind anonymously, returning 'success' which would
// be wrongly interpreted as the user having authenticated.
//
var username = "domain\username";
var password = "pwd";
if(password.length === 0)
{
    msg.payload = "Authentication failed - blank password";
    return msg;
}

// Bind to the rootDSE to test the user's credentials...
//
client.bind(username, password, function(err) 
{ 
    if (err)
    {
	    node.warn("Auth failed:" + err);
	    msg.payload = "Authentication failed";
	}
	else
	{
	   msg.payload = "Authenticated";
	}
	
    node.send(msg);
    
});

Very useful to check valid username/password in AD :+1:
I'd like to use it to authenticate AD users before they can proceed with other processes in the workflow. I can use a Form node to prompt for username/password and then pass these into the function node, but the password becomes visible to anyone who has access to node-red workflow which is a security issue (especially since they may use the same password elsewhere). Thus I wanted to be able to prompt for the login information within the function node and not make it visible [in clear text] anywhere in node-red - is this possible?

You don't have any connectivity between a function node and an end-user. The function nodes run in a sandbox on the server.

Two possible approaches spring to mind, I'm sure there are others.

  1. 1-way hash the password in the front-end.

    I'm not sure this gets you that much but it might add a little extra security. I don't think it would stop a determined attacker but you might not need to do that?

  2. Use an external service to do the authentication rather than Node-RED itself.

    By using a front-end proxy service, you can add security without needing to do anything in Node-RED. Most web servers can do this and act as proxies for node.js and other microservices, including Node-RED.

    This is, of course, more work and requires that you set up, manage and maintain an additional service. So it may be overkill for simple requirements. However, it is a tried and trusted approach and has the additional advantage that it is highly scalable.

Thx for providing some insights/thoughts on a solution.
Shame that the crypto module can't be used to encrypt and decrypt variable data actually within nodes i.e. encrypt the password value before it leaves the form node and decrypt it when its used in the function node so that when you look at secured variable values as a node-red admin they are always encrypted rather than plain text.

Your wording seems to imply a misunderstanding about how things work? A form runs in the browser. The nodes run on the server.

You could certainly add encryption to Dashboard using the ui_template node & you could manually create a form that encrypted the data before it was sent back to Node-RED. However, you would have a worse problem because you are now distributing the code to do the encryption to every client.

Doing a 1-way hash would be a slightly better choice (though bear in mind that I am not an encryption expert). Do the hash as early as possible and store the hashed value. While you can't then work on the hashed value because Node-RED doesn't know the actual data (only the hash), you can still compare hashes so you know whether a stored value matches one entered by the user.

This is still not very secure since you are doing things client-side which is always considered insecure. But you would at least not have the open password flowing round Node-RED.

However, this is always going to be a problem since your Node-RED admin users are always going to have access to sensitive information in Node-RED, it isn't designed to prevent that and to do so is a hard problem. If you look at something like Microsoft's Azure cloud platform, they have a specific key management store that integrates with other Azure services. Node-RED provides a basic credential store which is great but the key for the encryption of that is stored in the settings.js file that, by definition, running flows could access. So of itself, that does not (I don't believe unless someone else can correct me) provide a level of security that would pass a security audit without additional work to prevent access to the key.

I do know that various organisations have run security audits on Node-RED implementations but I've not seen any of those audits so I can't comment on what they did or what limitations they accepted (if any).

Ultimately, getting an external tool to do authentication and authorisation is almost certainly going to provide stronger security than using Node-RED alone. And is likely to be easier to maintain by separating out the concerns and letting Node-RED focus on what it is good at.

I found a way around it. Just some background on what/why I was trying to achieve:
I am using NR to communicate with a WLAN controller via APIs to allow users to generate unique per device access keys for a BYOD SSID. To make sure they are a valid company user before generating keys I need them to authenticate via AD. Using the ldapjs module seemed an ideal integrated solution until I figured that the username/password could be seen in clear text using debug node.

So instead I will do the auth using the WLAN controllers captive portal feature which supports AD and have that redirect the user to the NR key generation dashboard portal once they are authorised. Your pointer about using external auth service helped me to figure this as a solution - thx.

It does however make me wonder about suitability of NR in corporate type of applications as the data in it is easily accessible. Great for debugging, but not great for security. Even though I have got login security enabled to NR workflows, an admin can potentially get access to sensitive data. As I mentioned, it would be good to be able to have an "encrypt/decrypt" switch on static/dynamic variables so that they can be internally stored and passed around the system without the admin being able to see them in clear text.

The point to make I believe is that there is more to an enterprise service than running a node.js app.

You can certainly tighten up the security for a specific instance of Node-RED. I know that there are quite a few organisations doing this and who have done successful security audits.

You will also want to think about resilience - what happens if your Node-RED instance crashes? Security isn't just about controlling access.

The simplest thing that you can do is to set up a separate userid for running your Node-RED instance. This will help you control access inside Node-RED flows to system resources. You can also disable certain nodes, another feature that is already built in to NR. I understand security from architectural and governance viewpoints but I am certainly not an expert in terms of the details of securing NR instances or securing Linux platforms. Doubtless such an expert would find plenty of ways to lock down and otherwise secure the whole thing.

There is already a node that will make use of the NR credential store & any custom node can use it too.

You could certainly go further by making use of Node.js features to integrate further security features such as a keystore. I doubt this would be that hard to do thanks to the flexibility of NR but would require someone to build it.