Authenticating Node-RED using JSONWebToken - Part 2

Published

In Part 1 of this series, we got a first look at using JSONWebToken in Node-RED by learning how to encrypt and decrypt tokens with the node-red-contrib-auth package. In this article, we'll connect our application to a MongoDB database and lay the groundwork for a fully-authenticated application with authorized routes.

Install the node-red-contrib-mongodb2 Node

To process user login, we'll first need to connect to a MongoDB instance that stores our users. We'll use the node-red-contrib-mongodb2 node to make the connection. Install the mongodb2 package by clicking on the Node-RED main menu and clicking on Manage Palette. Select the Install tab and type mongodb2 in the search field. Then, click the install button next to the node-red-contrib-mongodb2 package.

Set Up the Endpoints

Let's start out by creating a simple user authentication flow. The user authentication flow will consist of three endpoints: a login form that accepts a username and password, a login processing route that will validate the users' credentials and return a JSONWebToken with that users' information, and a protected route that requires the user to present a token before it can be accessed.

First, we'll drag three HTTP input nodes onto the canvas. Configure the first node with a method of GET and a URL of /login. This route will present the user with a login form.

Configure the second node with a method of POST and a URL of /process_login. This is where the user credentials will be sent to be validated and the JSONWebToken generated from those credentials.

Finally, configure the last HTTP input node with a method of GET and a URL of /protected. This will be the route that is inaccessible unless the user is authenticated.

Create the Login Form

Next, we'll create a simple login form in HTML with a username and password text field and a submit button. Drag a template node into the canvas from the palette and place it near the output of the HTTP input node with the URL of /login.

Then, double-click the template node and add an HTML form with an action of /process_login and a method of POST.

<html>  
   <head>
   </head>
   <body>
      <form action="/process_login" method="POST">
         <label for="username">Login</label>
         <input name="username" type="text" />
         <br />
         <label for="password">Password</label>
         <input name="password" type="password" />
         <br />
         <input type="submit" />
      </form>
   </body>
</html>  

Finally, drag an HTTP response node onto the canvas and wire everything up.

Add Login Processing With mongodb2

Processing login means we need to store our users in a database. We'll store our user's login credentials using MongoDB and connect to it using the mongodb2 Node-RED node.

The first thing you'll need is a MongoDB instance. You can spin up a Compose MongoDB database with SSL enabled or start your own local instance of MongoDB. Starting a new deployment on Compose is the easiest way to get started.

Next, connect to your MongoDB deployment using the mongodb2 node. You can find instructions for doing so in the previous article in this series. Once you've configured your mongodb2 node, we need to add a user to our database. We'll do this using the inject node, which will allow us to inject data into a flow by clicking a button.

Drag an inject node onto the canvas and double-click it to configure. In the payload section, click on the timestamp drop down and select {} JSON. Then, paste the following user information into the text box in payload (you can substitute with your own info if you'd like):

{ "username": "admin", "password": "secret", "firstName": "John", "lastName": "O'Connor", "email": "johnwoconnor@compose.io" }

For now, we'll store this password in plain text since we're using a simple authentication mechanism. In practice, you never want to store your passwords in plaintext - instead, you'd want to encrypt your password using something like bcrypt.

Then, drag the mongodb2 node onto the canvas and double-click it to configure. Type Users in the Collection section of the configuration and select insert from the Operation drop-down.

We'll also put a debug node near the output of the mongodb2 node so we can see any messages that are returned from the node. Wire everything up and click Deploy.

Finally, let's inject our new user into the database. Click on the button attached to the inject node to send the JSON object you defined above into the database. You can double-check that the user exists by going to the Compose console and viewing the collection.

Now that we have some data in the database, we can retrieve that data and securely store and pass it around using JSONWebToken.

Encrypt User Data into a JSONWebToken

Let's start out by using our login processing to retrieve a user from the database. Drag the function node onto the canvas near the HTTP input node with the POST method and /login route. Then, double-click to add the following code to the function:

msg.userData = msg.payload;  
msg.payload = {  
    username: msg.userData.username
};
return msg;  

This will save the form data, which is passed into the function, into the msg.userData object. Then, we'll modify the msg.payload so it can be passed into the mongodb2 node as a database query. Next, drag a new mongodb2 node near the output of the function and double-click it to configure. Use the findOne operation and type Users in the collection field (make sure the collection name matches the collection you inserted the user into earlier).

JSONWebToken is agnostic to the login mechanism you use. For simplicity's sake, we'll ignore the actual password validation for now and just assume that the login was successful. In practice, you can choose any method of authentication to validate the user's login credentials.

If you already have a JSONWebToken Token Configuration from the previous article in this series, you can skip the next paragraph. Otherwise, you'll need to create a Token Configuration.

To create a token configuration, you'll first need to find the JSONWebToken node in the palette and drag it onto the canvas. Double-click it to open the configuration panel, then click the pencil icon next to the drop down that says Add new JsonWebToken_config. Give the configuration a unique name, and enter a random set of characters in the secret section. You want the secret to be random and not guessable - this will serve as the key you use to encrypt and decrypt the JSONWebToken.

Now that you have a Token Configuration, you can pass the user data into the JSONWebToken node to encrypt it. If you haven't already done so, drag a JSONWebToken node onto the canvas after the output of the mongodb2 node and double-click it to configure. Select the Token Configuration you created earlier from the drop-down and name the node encrypt (or anything you'd like really).

Now that we've configured our JSONWebToken, we can start using it. The JSONWebToken node saves the encrypted token in the msg.token object. Since we want to send the token as output, we'll move the msg.token generated by the JSONWebToken node over to the msg.payload using a change node. Double-click on the change node and configure it to SET the msg.payload to the value of the msg.token.

Since we started with an HTTP input node, we need to add an HTTP response node to complete the response. Drag an HTTP response node onto the canvas next to the JSONWebToken node, wire them all together, and hit DEPLOY. When you submit the login form, the generated token will be returned as the response.

Now that we have the login form wired up, navigate to your login form by going to http://yourserver/login in your browser, replacing yourserver with the location of your Node-RED installation. Enter admin into the username section of the login form, and anything as the password (we're ignoring the password for now). When you click submit you should be redirected to a web page with your JSONWebToken, which looks something like the following:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ODQ4NDI2NWVmYTY0MDAwMWM2ZDBlZDAiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNDgyMTg0NDI0fQ.86PuteGK1JxpfsHhhckkqElbcseoCjAqpe083kRzCko

This JSONWebToken contains the encrypted data for your user and can only be decrypted by using the same secret key you used to encrypt it. Let's see how that decryption process works.

Decrypt User Data from a JSONWebToken

Your JSONWebToken from the previous section now contains encrypted data about your user. To decrypt the data, we just need to run it through the JSONWebToken node again. Assuming you use the same key and Token Configuration, the JSONWebToken node will decrypt any data stored in the msg.token object.

Now, let's test this on our /protected route. Navigate to yourserver/protected in a web browser and pass the JSONWebToken as a query string parameter called token, so a call to our /protected route might look something like this:

yourserver/protected?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ODQ4NDI2NWVmYTY0MDAwMWM2ZDBlZDAiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNDgyMTg1MDcyfQ.KxmCfxj8rGKbkZu_Wfu75lHRIPjWjpH_apY58HnJgS0

As a reminder, make sure you replace yourserver with the location of your Node-RED installation.

Node-RED will place the token query string parameter into the msg.payload.token object. Since the JSONWebToken expects the token to be in the msg.token object, we'll need to move it using a function node. Drag a function node onto the canvas and double-click it to configure. Since there are two outputs to the function, we'll also need to update the outputs section on the bottom of the function configuration. Change the value in the input field from 1 to 2. Then, add the following code to the function node:

if (msg.payload.token) {  
   msg.token = msg.payload.token;
   node.send(msg);
} else {
   msg.statusCode = 403;
   node.send([null, msg]);
}

Here, we're using the node.send API to conditionally send a message on one of the two outputs of the function.

The first part of the conditional will send the msg.token to the JSONWebToken node, and the other will return an error to the user by setting the msg.statusCode to 403 (Unauthorized) and passing it directly to an HTTP response node.

With the error condition handled, we'll drag the JSONWebToken node onto the canvas and double-click it to configure. Make sure you select the same configuration from the dropdown that you used to encrypt the token.

The JSONWebToken puts the decrypted message back into the msg.token object. Since we want to send it back to the user, we'll need to transfer that over to the msg.payload object. Let's do this by dragging a change node next to the JSONWebToken node and setting the msg.payload to the value from msg.token.

Finally, we'll drag an HTTP response node in and wire them all together. Since we have two outputs from our function node, make sure sure to wire the first output of the function to the JSONWebToken node. You should already have the second output wired to an HTTP response node. Then, click DEPLOY to deploy the changes.

Let's test it out by calling our /protected route and passing in our JSONWebToken using the token query string parameter. The final route looks like the following:

http://yourdomain/protected?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ODQ4NDI2NWVmYTY0MDAwMWM2ZDBlZDAiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNDgyMTg1MDcyfQ.KxmCfxj8rGKbkZu_Wfu75lHRIPjWjpH_apY58HnJgS0

and should produce the following result:

{"_id":"58484265efa640001c6d0ed0","username":"admin","iat":1482185072}

Wrap Up

Now that you have all the pieces together, you can start wiring up secure applications that connect to local or a cloud-hosted database like Compose MongoDB. There are some improvements we can still make, such as storing and validating the users' login credentials securely, but this should provide a good starting point.


If you have bits you think should be in NewsBits, or any feedback about any Compose articles, drop the Compose Articles team a line at articles@compose.com. We're happy to hear from you.

Image by Ryan McGuire
John O'Connor
John O'Connor is a code junky, educator, and amateur dad that loves letting the smoke out of gadgets, turning caffeine into code, and writing about it all. Love this article? Head over to John O'Connor’s author page to keep reading.

Conquer the Data Layer

Spend your time developing apps, not managing databases.