Part 6: Node + Express for a ​S​imple ​S​ecurity ​M​odel

Chris Berry JavaScript, Node, Single-Page Application, Tutorial, Vue Leave a Comment

Part of the Solid Foundations Learning Series

This is an in-depth learning series focused on a specific application: a JavaScript-based suite of single-page applications optimized for use in a microservice environment. We focus on telling the story of “why” and “how” it was built. 


Throughout this series, we have touched on adding navigation, content and single-page applications, but we haven’t touched on the security of the application yet.

In this article, we’re going to add a simple security model to the application which will accept a login, validate a user, redirect to a secure page, enable a logout, and catch any errors which occur during the process. Let’s get started.

Creating A Security Model

The first thing we’re going to do is create a service for the Node server. This will do our authentication of a user, expose our logged-in user information to the application, and handle the invalidation of the user once they log out of the system.

Now, remember, we’re calling this a simple security model, so be aware that there is no database involved. There are no hashed passwords. There are no tokens passed around. We’re going simple just to demonstrate the idea of security. Now let’s take a look at the user.service.js module:

module.exports = {
 status: "",
 user: {
   userName: "admin",
   password: "admin",
   authenticated: false,
   status: this.status
 },
 validateUser: function(login) {
   console.log("this.user: ", this.user, " and login: ", login);
   if (
     login.username === this.user.userName &&
     login.password === this.user.password
   ) {
     this.user.authenticated = true;
     this.status = "You have successfully signed in.";
     return true;
   } else {
     this.status = "Invalid useranme or password.";
     this.user.authenticated = false;
     return false;
   }
 },
 getUserInfo: function() {
   if (this.user.authenticated) {
     return {
       username: this.user.userName,
       authenticated: this.user.authenticated,
       status: this.status
     };
   }
 },
 logoutUser: function() {
   this.status = "You are now signed out.";
   this.user.authenticated = false;
 }
};

From top to bottom, I’ll walk you through this code file. First, we’re creating a simple module exports which expose a handful of functions and properties. We are exposing both a status and a user at this time. Of the two, we’ll be using the User object the most throughout the execution of logging in and logging out.

The first function is validateUser which is looking for a simple parameter called login. This parameter will be an object which contains the username and the password being passed in from the user interface. If both parameters match the static user information which we have in the user object, we will set a user.authenticated property to true and set the status to a success message.

The second function of the module is getUserInfo. This function will return a simple user object back to the calling code. Because of the need to be authenticated, we first check our internal user object to determine if it has been authenticated. If it has, we then create a literal object on-the-fly containing the username, the current status of the user, and a boolean flag of authenticated.

Our last function is a simple logoutUser function which accepts no parameters. The function will set the current status to a signed out status and flips the authenticated status flag to false.

Now that we have created our security model, let’s go on and use it for logging in and enabling a secure page for our user.

Securing Our Routes And Display A Page

In our routes directory and inside the index.js file, we have all of the routes for our application. At this point, we’ve added in several more to enable logging in and logging out and for our secure page.

The first route that we have created has been a simple GET route to direct the user to a login page.

app.get("/login/", function(req, res, next) {
     let data = {
       title: "Sign In"
     };
     res.render("static/login", { data });
   });

The markup for that page looks like this:

{{> header }}
{{> navigation }}

<div class="row justify-content-center align-items-center" style="height:60vh">
           <div class="col-4">
               <h2><i class="fa fa-sign-in" aria-hidden="true"></i> Sign In</h2>
               <div class="card">
                   <div class="card-body">                       
                       <form action="/checklogin" method="POST" autocomplete="off">
                           <div class="form-group">
                               <label>Username</label>
                               <input type="text" class="form-control" name="username">
                           </div>
                           <div class="form-group">
                               <label>Pasword</label>
                               <input type="password" class="form-control" name="password">
                           </div>
                           <button type="submit" id="sendlogin" class="btn btn-primary">login</button>
                           &nbsp;
                           <a href="/" class="btn btn-outline-secondary">cancel</a>
                       </form>
                   </div>
               </div>
           </div>
       </div>

{{> footer }}

This page has a simple Bootstrap 4 form centered in the page. There are two text fields on the form along with a submit button. The fields are looking for the username and for the password. Upon clicking the submit button, the form will POST back to the server using this code:

 <form action="/checklogin" method="POST" autocomplete="off">

Notice that we’re posting to the /checklogin route with the information from the form. Now for the checklogin route code:

app.post("/checklogin", function(req, res, next) {
     let login = {
       username: req.body.username,
       password: req.body.password
     };

     let authenticated = userService.validateUser(login);
     if (authenticated) {      
       // simulating a database call
       let timer = setTimeout(function() {
         user = userService.getUserInfo();                
         clearInterval(timer);
         res.redirect(302,"/secure");        
       }, 1000);
     } else {      
       res.redirect(302, "/login/");
       res.end();
     }
   });

From the request, the code is going to look for two parameters sent back to the server. We’re going to assign those two values to an object literal called login’ with the properties of username and password. We then create a local variable called authenticated and capture the return value from our userService.validateUser function after we pass in the login object.

See Also:  Release: Hyperledger Blockchain Analytics Tool

Assuming that we have a valid login object, authenticated will pass the if test and now sets a global user object with the contents that we requested from the userService.getUserInfo function. We’ve surrounded the setting of the user object and the redirect with a time out to simulate hitting a database. Once the timeout finishes, we use the response object’s redirect function to set a response status of 302 (found) and redirects the user to the /secure route. If we fail to authenticate we are going to simply redirect the user back to the login form and kill the response.

We’ve logged in and we’re being redirected to another route. This is great, but what does this new route do for us? Let’s take a look at the code:

app.get("/secure/", function(req, res, next){
     if(user.authenticated){
       let data = {
         title: "Secure Page",
         links: fileListing.CreatedFileList(enums.RouteEnums().unknown),
         contents: "",
         user: user,
         active: true
       };
       res.render('static/secure', { data });
     } else {
       res.redirect(302, "/login/");
       res.end();
     }    
   });

Once we hit the /secure/ route, the first thing we’re going to do is check to see if the user can be here by checking the user.authenticated property. Remember, if we successfully logged in, this property should be true.

Upon passing through the if statement, we can now set the actual page data as we have been doing for most of our other pages. Notice that on the links property we’re still using the fileListing.CreatedFileList function. This time though, we’re going to be passing a value of unknown to the listing because the file is not part of the view/pages directory. This was done so that we can still have global navigation across the top of the application. We also have a user property where we will assign the global user object to it. This way we can utilize the information about the user on the views we access.

Once our data object is set, we now will render the view static/secure view to the browser. If at any time we are not authenticated, this route will redirect the user back to the /login/ route for the user to try again.

See Also:  [Video] DevOps Orchestration: Kubernetes, OpenShift & Cloud Foundry

Our secured page doesn’t have much going on with it right now, but we do utilize the information about the user in it. Here is the Handlebars view:

{{> header }}
{{> navigation }}

<h3><i class="fa fa-lock" aria-hidden="true"></i> Signed In</h3>
<hr />
<p>{{data.user.status}}</p>

{{> footer }}

Remember that we set the status of the user object during login? Now we’re going to display that information using the Handlebars double curly brace helper and display the data.user.status to the user.

Now for the navigation bar. To begin with, the navigation has a sign in link, but we want to be able to change that depending on the status of the user. Let’s take a look at the markup for the navigation:

<div class="navbar-nav">
        {{#if data.user.authenticated}}
      
         <span class="text-light navbar-text">Welcome {{data.user.username}} &nbsp;</span>
         <a href="/logout" class="nav-link">Sign Out</a>
      
       {{else}}
     
         <a href="/login" class="nav-link">Sign In</a>
     
       {{/if}}
     </div>

We’re going to use one of Handlebars view helpers and do an if conditional on the data.user.authenticated property which we set back in the /secure/ route. If this passes, the markup allows the user name to be displayed using the curly braces and it will display a new hyperlink which directs to the /logout/ route. If the conditional fails, the user of the application only sees the link for signing in.

Now that we’ve gone through signing into the application and getting redirected to a secure page that checks our authentication, let’s go ahead and sign back out. Clicking the link found in the navigation bar will send us to a simple GET route called /logout which contains the following code:

 app.get("/logout", function(req, res) {
     userService.logoutUser();
     user = {};
     res.redirect(302, "/");
   });

This route does three simple things. We set the authentication of the users authenticated flag to false using the userService.logoutUser function. We set the global user object to an empty object literal. And we now redirect the user to the index of the site.

Wrapping It Up

There we have it. Our simple security model has been implemented which allows a user to login and authenticate via a service. The valid user is then redirected to a secure page and the navigation bar is updated with the status and changes from Sign In to Sign Out. This model can definitely be expanded by adding database access to store additional users. Passwords should be stored in some non-human readable form when in the database. JSON web tokens should be generated and passed to the front end to validate any request made back and forth from the front end and the back end.

As you can see, the security of an application can quickly go down deep rabbit holes of locking an application down. But by implementing this simple model, we now have a pretty good idea of where to start. In future segments of this series, we will talk about securing single page applications and securing API endpoints. Stay tuned for more.

Series Quick Links

Part 1: JavaScript Application Introduction
Part 2: Navigation Setup with Node + Express
Part 3: Using Node and Markdown + the Package Showdown
Part 4: Adding Smaller SPAs to An Existing Application
Part 5: Diving into the Vue.js SPA
Part 6: Node + Express for a Simple Security Model

What Do You Think?