Architecting a Secure RESTful Node.js app16 min read

Architecting a Secure RESTful Node.js app

In this post, we will take a look at architecting a Node.js app that will act as a REST API server for all of our clients. We will be implementing a Token Based Authentication to authenticate and authorize the client to access the application data.

This application design is targeted at systems that would like to implement its own REST API with a custom authentication. To showcase how one can consume the REST API with authentication, we will be writing an Angular.js client.

This post is inspired by Authentication with AngularJS and a Node.js REST api.

You can find the completed code here.

So, let us get started.

REST API

If you are new to REST API, I would highly recommend watching the below video

Also do checkout restapitutorial.com and HTTP Status codes for more info.

Security

If you are new to implementing security or want to gain a fair idea, I would recommend watching the below video

 

Architecture

The key difference between a traditional application server and a RESTful server is that the RESTful server dispatches only data for an end point and not a web page. This is very advantageous when you want to have a mobile app or a tablet app built on top of your existing application data. This way the time to market your app idea is very low. You build your application logic only once and then consume it from any RESTful client.

Now the question which arises is how do we know if the client accessing our REST API is genuine? Here is one of the possible solution.

I have used the below architecture with a couple of my projects and it seemed to be pretty sturdy.

minimal

As you can see from the above diagram, the architecture is pretty flexible and can be used by any client that can consume a REST API.

Any client which would like to access the data from our system should first fire a request to a /login route (or endpoint) with a valid username and a password. We then check if the credentials are valid. If they are valid, we would send an user object back to the client along with an access token.

This token has an expiration time associated with it. And the client is expected to send this token to the server everytime a request is made to fetch the data. This is our custom way of authenticating a client.

For your app, you can either issue a username/password for your client (an Android app or Angular app) to connect and make API calls or you can use your customer’s credentials to make the call too.

As soon as a user makes a call to one of our API endpoints, we redirect the request to the authentication middleware. This middleware is responsible for authenticating the client by first processing the token. If the token is not present/invalid or expired, it will throw a 401 HTTP status code. If the request has a valid token and is not yet expired, we will invoke the next middleware in the stack.

If you want, you can also add another layer of security. You can request the client to send the logged in user’s username along with the token. This way, first we will authenticate the token and then the client. This is authentication and authorization.  This approach will also help you to check if a given user is allowed to access a route and its data.

Simple and easy right? We will implement the same now to get a feel of the architecture.

Setup the REST Server

Create a new folder named myRESTApp. Inside that folder, create a folder named server. We will first create a new node project. Open a new terminal/prompt from inside the server folder and run

npm init

Fill it up as applicable. Now, we will add a few dependencies. Run

npm install --save express body-parser morgan jwt-simple

We are going to use Express as our server side framework, jwt-simple module to generate JSON Web Tokens. These tokens will be used as access tokens between the server and client.

Our final package.json would look like

Now, we will setup our server. At the root of server folder, create a file named server.js. This will be the entry point into our node application.

Update server.js as below

Things to notice

Line 28 : The middleware that we are going to write to authenticate and authorize the request.

Line 30 : The list of routes for our application. We will create this file soon.

Line 42 : Start the server

Line 11 : Enable Cross Origin Resource Sharing

Remaining config is pretty standard Express stuff.

[Important] : You should consider adding HTTPS support to you app. Not only for login, but for every request. You can find more info about setting up HTTPS here. Thanks to Ward Bell for pointing it out.

Now, we will work on the routes. For the purpose of this post, we will create 3 types of routes

  1. Routes that do not need authentication or authorization like the login. Or an About Us, Contact details etc.
  2. Routes that need authentication to access the underlying resource. Like a list of products or any data that would be specific to the user.
  3. Routes that need authentication as well as authorization. Like deleting users or updating configs etc. (like an admin task). For this authentication alone would not suffice, you need to be authorized to access that resource.

Create a new folder named routes inside the server folder. Inside the routes folder create a new file named index.js. This file will hold all the routes needed for our app.

Updated index.js  as below

Things to notice

Lines 4 – 6 : Externalized the business logic for each route

Line 11: This is the login route. A client that would like to interact with our data, should first hit this URL, get the token and then only will be authorized to access the remaining routes

Lines 16 – 20 : A dummy set of API methods to access a product. This can be any resource you would like to expose to your clients. Do notice the /api/v1/ at the beginning of the route. This is cue for the authentication middleware to validate the request. If you do not provide the /api/v1/, the validation will be skipped. As you can see from line 11.

Line 25 – 29 : A dummy set of Admin API methods to manage a user. Do notice the /api/v1/. 

Now, we will build the logic for each of the 3 routes. First Login. Create a new file named auth.js inside the routes folder. This file will contain the logic to authenticate the user and generate an access token when the login is successful.

Update auth.js as below

Things to notice

Line 10 : If the username or password is invalid/not present, we will throw a 401

Line 20 : At this point, we will fire a call to DB and check if the credentials are valid. To keep this example simple, I have spoofed the response as a success & built a dummy DB response.

Line 23 : If the authentication fails, we send back a 401

Line 36 : If everything is fine so far, we respond with a token

Line 65 : This method is responsible for generating the JSON response that is expected from the login endpoint.

Line 67 : Generates the token with the provided expiration date and a app secret. We will create the app secret in a minute. Do note that the app secret is the only hook with which the decoding happens. If you change the secret after you dispatch a token, it may not be valid when it comes back from a client.

Now, we will create the secret. Create a new folder named config inside the server folder. Inside the config folder create a new file named secret.js and update it as

Now, we will create the dummy API for products and users. Create a file named products.js inside the routes folder and update it as below

And create another file named users.js and update it as below

Now, we will create the validation middleware. Create a new folder named middleware  inside the server folder. Inside the middleware folder create a new file named validateRequest.js. This will be the validation layer for the API requests.

Update validateRequest.js as below

Things to notice

Line 13,14 : We search for a token/key and in the request. This is the access token we have issued post successful login and key is the current logged in user’s username.

Line 18 : We decode the token and check its validity on line 20

Line 31 : We hit the DB to check if the current user is a valid user and also fetch his role.

Line 35 : We validate role vs url accessed and allow or deny accordingly.

All the other else cases would dispatch appropriate HTTP status codes.

That is all we need to build a secure REST API.

Testing the REST API

The process is pretty simple. First, we will fire a request to the login end point with dummy credentials. The response will contain a user object and the access token. Since we have spoofed the response to be a success, you will get a success response always. In your actual app, update this login to valid authentication.

Using that access token, we will make further request to our API.

I will be using CURL to fire the REST requests, you can use any REST client to do so.

Start our server by running

node server.js  or  nodemon server.js

Brute Force

Without any token, let us fire a request to get all the products

URL : curl http://localhost:3000/api/v1/products

Screen Shot 2014-09-21 at 11.25.11 pm

As expected you will get a 401. You can also dispatch a 403 unauthorized here.

Login

Now, let us try logging in.

URL : curl –data “{\”username\” : \”[email protected]\”, \”password\” : \”pass123\”}” -H “content-type:application/json” http://localhost:3000/login

The username and password above are dummy. Do notice the header content-type. Without this, Bodyparser will not kick in. And the response would consist of a token and the user object.

Screen Shot 2014-09-21 at 11.30.01 pm

Now, we will make a copy of the token and we need to send this in the headers from now on.

Fetch Products

To fetch all products, you would run

URL : curl -H “content-type:application/json” -H “x-access-token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MTE5MjcyMDAzNjl9.cuUKFKsf2qhQJHToP-zBmObhMwi84rhnrhH03OdyzSA” -H “x-key:[email protected]” http://localhost:3000/api/v1/products

Do notice that I have updated the token in the above URL. Make sure you update the token before firing the request.

Do notice that we have another header named key. This will consist of the current logged in user’s username.

And we would get the response back as

Screen Shot 2014-09-21 at 11.35.18 pmAdmin resources

Since we have hardcoded the role for the current user to be admin, we will be able to access the admin URL’s too. You can fire a request like

URL : curl -H “content-type:application/json” -H “x-access-token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MTE5MjcyMDAzNjl9.cuUKFKsf2qhQJHToP-zBmObhMwi84rhnrhH03OdyzSA” -H “x-key:[email protected]” http://localhost:3000/api/v1/admin/users

And you should see the response like

Screen Shot 2014-09-21 at 11.37.08 pmNow, let us test the authorization part. Navigate to routes/auth.js line 56, set the role to ‘user’ and restart the server

Now, fire the request to get all users

URL : curl -H “content-type:application/json” -H “x-access-token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MTE5MjcyMDAzNjl9.cuUKFKsf2qhQJHToP-zBmObhMwi84rhnrhH03OdyzSA” -H “x-key:[email protected]” http://localhost:3000/api/v1/admin/users

and you should see a “Not Authorized” error as expected.Screen Shot 2014-09-21 at 11.40.59 pmSimple and easy right!!

Angular.js Client

Now, we will build an Angular.js client that will consume the above API. Inside the myRESTApp folder, create another folder named ngClient. Open a new terminal/prompt here.

We will use a slush generator named slush-ng (written by me) to scaffold a minimal Angular.js app. For that we will install gulp, slush and slush-ng globally. Execute

Windows Mac/*nix
npm i -g gulp slush slush-ng    sudo npm i -g gulp slush slush-ng

Once this is done, from inside the ngClient folder, run

slush ng

You can provide any name to your project. And slush will scaffold a minimal angular app and download the required dependencies.

To run the Angular.js app as is, we will run

gulp

This will start a new server on port 2772. Now you can navigate to  http://localhost:2772 to see the scaffolded app in action.

This app is configured with routes, a sample directive a sample filter and an integration with the service layer. All you need to start off with a minimal Angular.js app.

Authentication Layer

The first thing we are going to do is setup an authentication factory on the client. Create a new folder named auth inside the js folder. Inside this folder, create a new file named auth.factory.js.

Update auth.factory.js as below

Things to notice

Line 1 : AuthenticationFactory. This Factory is responsible for checking the user status on the client side.

Line 17 : UserAuthFactory. This factory is responsible for contacting the login endpoint and validating the user. And also logging out the user.

Line 44 : TokenInterceptor. This factory is responsible for sending in the access token and the key along with each request to the server.

Next, we will create a controller to manage the login. Create a file named auth.controller.js inside the auth folder. Update it as below

Things to notice

Line 3 : We hard code a user, for easy testing (lazy)

Line 14 : We fire a request to the Login endpoint

Line 16 – 22 : We set client side session variables. We store the values in the AuthenticationFactory as well as sessionStorage. This way, if the user refreshes, we will still have the session on the client side.

Line 24 : Upon successful login, we will redirect the user to the home page.

On the client side, we will create a partial for Login. Inside the partials folder, create a new file named login.html and update it as below

This partial will be associated with a Login Controller, which we created in auth.controller.js.

Now, we will setup our app. Open js/app.js and update it as below

Things to notice

Line 5 : We have added the TokenInterceptor.

Line 8 : New route /login

Line 12,18,24,30,36 : Setting if the current route needs authentication.

Line 43 : We set up a few session variables to keep a track of user’s session. These variables are stored both in memory as well as browser’s session storage (in case of a full page refresh we do not want the user to login again).

Now, we will add the logout link. Open partials/header.html and update it as below

Things to notice

Line 4,12showMenu expression will be true when the user is logged in. This way, we do not show the menu to the user if not logged in. This value will be updated in app.js line 58.

Line 21 : The logout link

Now, the functionality for the logout link. Open controllers.js and update the HeaderCtrl as below

The code so far will take care of the login. Now we will make a request to get all products after successful login.

We will create a new dataFactory to interact with the REST API. Open factory.js and update it as below

A sample route to demonstrate the access of API methods post login.

Now in controllers.js update the Page3Ctrl as below

Pretty straight forward. Access the Factory, get the products and finally update the UI. Open partials/page3.html and update it as below

Finally the index.html. Update it as below with the newly created js references

That is it. Back to browser (with gulp command running in the background) and navigate to  http://localhost:2772. You should see a login page

Click on Submit and you should see the home page. Now click on logout and you should be logged out. Log back in and click on Page 3 (Sample Factory) link in the menu. Now, you should see a list of products from the REST endpoint.

Screen Shot 2014-09-22 at 9.02.40 am

Simple, easy, secure and scalable architecture for your awesome apps!


Thanks for reading! Do comment.
@arvindr21