Architecting a Secure RESTful Node.js app

Tweet about this on TwitterShare on LinkedIn47Share on Google+26Share on Reddit0Buffer this pageFlattr the authorEmail this to someonePrint this page

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

Tweet about this on TwitterShare on LinkedIn47Share on Google+26Share on Reddit0Buffer this pageFlattr the authorEmail this to someonePrint this page
  • http://kolyjjj.github.io koly

    when you log out on the front end, you just delete the session storage token and there is no request to the backend. So this is just a logout on the front end. After the log out, the user can still use the previous token, is this ok?

  • Omkar

    Excellent Tutorial…However we can make it more loosely coupled ……kinda tightly coupled right now… divide everything into modules

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Can you elaborate?

  • WonderHeart

    Thanks for the detailed article. I have one query though- For the first
    time user signs up with his user credentials. Where are these
    credentials stored initially? are they encrypted and stored in database?
    I am
    asking this because, i want to know how the server knows to validate the
    user whether he is logged with correct password or username. He should
    be validated against something, right?

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Yes, you need to have a sign up page for on boarding the user.

  • Віктор Борщов

    Thank you for a great tutorial!
    I have a problem. Here https://bitbucket.org/snippets/vborshchov/bKbap I have a code with changes in validate function. And I cannot login to my app, every time I get response with message “Invalid credentials”. I have discovered that line 2 executes after lines 4-19. How can I change the execution order?

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      You are mixing sync with async.

      and your validate function would be

      Pls. understand the difference between async and sync before you start building an application with security in Nodejs : http://stackoverflow.com/q/27659116/1015046

  • JUNIOR FERREIRA

    I did not like , lacked access to MongoDB is here my criticism !

  • Yves Dufour

    Hi Arvind

    Thanks for this clear article…

    I run all the steps ..; server is running fine, I can test it with curl request..

    basic generated Anguler app is also running fine..

    however after performing all file modifications , and requesting again http://localhost:2772 I don’t get the login page…

    only a blank page

    source code of the page is

    Did I miss any important update ?

    yves

  • Marco Solari

    Did you voluntarily use $window.sessionStorage instead of $window.localStorage ?
    I’d expect to be always logged in – even after closing and reopening the browser (or session) – at least until JWT expires… Am I wrong? I don’t understand what’s the advantage to specify a 7-days expiration time for tokens, if you have to re log in everytime you access the site… I’d espect a behavour like google.com, where you stay logged across sessions for some weeks…

  • Harshad

    JWT now trims the expiry token

    Instead of 1420636467027 now you get 1420636467 in “exp”.

    Also there is no need to manually check token expiry. JWT err can be made useof here.

    return jwt.verify(token, config.secret, function (err, decoded) {

    if(err){

    if(err.name == “TokenExpiredError”){

    return res.status(400).json({ error: “Token expired” })

    }else if(err.name == “JsonWebTokenError”){

    return res.status(401).json({ error: “Invalid Token” })

    }

    }else if(decoded){

    next();

    }

    });

  • Luis Florez

    Hello Arvin.

    Great article, thanks for sharing your knowledge.

    Just one question, my collapsed menu is no showing when I click it, any ideas why this might be happening?

    the normal menu shows and works perfect.

    Thanks!!

  • jnuc093

    I tested successful by command line curl.
    is this right test by Postman?

  • https://franciskim.co Francis Kim

    Excellent article – thanks so much.

  • Roemer Blom

    How could I make it so that only registered users can login, because at the moment I’m able to login just by entering something in the input fields.

  • jf pwork

    Hello & Thanks,

    as a window follower (;-( got that…

    $ npm i -g gulp slush slush-ng
    [OK]

    Hope it could help you

    Best regards
    —->

    [email protected] /cygdrive/c/a/b/_c/d/e/f/g/ngClient
    $ slush ng
    C:UsersXAppDataRoamingnpmnode_modulesslush-ngslushfile.js:35
    userName: format(user.name) || osUserName,
    ^
    TypeError: Cannot read property ‘name’ of undefined
    at C:UsersXAppDataRoamingnpmnode_modulesslush-ngslushfile.js:35:26
    at Object. (C:UsersXAppDataRoamingnpmnode_modulesslush-ngslushfile.js:38:3)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)
    at Liftoff.handleArguments (C:UsersXAppDataRoamingnpmnode_modulesslushbinslush.js:90:3)
    at Liftoff.launch (C:UsersXAppDataRoamingnpmnode_modulesslushnode_modulesliftoffindex.js:153:6)

    [email protected] /cygdrive/c/a/b/_c/d/e/f/g/ngClient
    $ node -v
    v0.12.7
    $ gulp -v
    CLI version 3.9.0
    $ npm-v
    2.11.3

    • jf pwork

      error come from the fact that no username and email are set globaly into .gitconfig
      $ git config -global user.name = “yourname”
      $ git config -global user.email = “[email protected]

  • Priti

    can you plz explain ?

    for what purpose we will install

    gulp, slush and slush-ng globally.

  • Priti

    How will it work with jade

  • david lopes

    Thx guy, I was in trouble about angularJs token-base auth, your front-end solution is mind blowing, thx a lot!

  • wilsonfpz

    Nice article, could you explain why not just return config but return config || $q.when(config); ? thanks.

  • Mohsin

    Hi, Thanks for the post,

    When i run the following command getting following error, anything am i missing?

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

    curl: (7) Failed connect to –data:80; Operation timed out

    curl: (3) [globbing] unmatched brace at pos 19

    curl: (7) Failed connect to :80; Connection refused

    curl: (7) Failed connect to myapp.com”,:80; Operation timed out

    • Gareth Patterson

      I’m seeing same issues with curl and -data… it seems to be putting the options after the host. Put the URL immediately after curl and then –data, -H etc

    • Gareth Patterson

      Never mind… I’m wrong… I just used Postman instead… it works fine. (REST browser plugin)

      • Jaden Ng

        Hi,

        May I know how to use Postman for the login session. Thanks :)

        • http://thejackalofjavascript.com/ Arvind Ravulavaru

          Take a look at https://youtu.be/JZP2ose-OBQ you can search on YouTube for more.

          • Jaden Ng

            Hi,

            I followed the video but still encountered problem :(

          • http://thejackalofjavascript.com/ Arvind Ravulavaru

            Try a raw request. With content type as application/json. And in the raw data can be a JSON like

          • Jaden Ng

            Hi,

            It works now.

            I tried raw request(JSON) with the following data,

            {“username”:”[email protected]”, “password”:”pass123″}

          • Jaden Ng

            Hi Arvind,

            May I know how to use POSTMAN to fetch product by using token and key. How to fill the information into POSTMAN. Thanks

    • HBI de SF

      beware of the following font problems that will result in CURL command errors:
      – straight quotes (“”) that appear as awful smart quotes (“”)
      – double dash (–) that appear as an extended dash (–)

      Here is the remedy:
      – paste code into Notepad
      – CTRL+H: find “ and replace with ”
      – CTRL+H: find ” and replace with ”
      – CTRL+H: find – and replace with —

  • Hermes Sánchez

    Help me please!, In my application I have token-based security setup. However, ANGULAR-FILE-UPLOAD doesn’t use $http and instead relies on XHR (think it is so). How would I go about intercepting an XHR request or just specifying headers to be send with the upload request? Thanks in advance. :(

  • Hermes Sánchez

    Excelente articulo, un aporte importante. Muchisimas gracias por compartir sus conocimientos.

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Thanks.

  • Edisqus

    “Extra.. Extra.. Read all about” – also, I think you too like The Who.

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      LOL!

  • Edisqus

    One may have to give full control permissions to certain directories in order for files to be saved. Thanks a lot!

  • 김영덕

    Thanks for AMAZING article Arvind! It’s really Awesome but, I have some questions.
    What does “delete this.user;” mean in AuthenticationFactory. I think function “check” is a method so it will be bind with ‘auth’ object. But, there is no ‘user’ property in ‘auth’ Object. Can you explain it?
    Also, in ‘UserAuthFactory’ (delete AuthenticationFactory.user, delete AuthenticationFactory.userRole)
    I can’t figure out what is “user” and “userRole” properties. There are no such properties in AuthenticationFactory.

  • Andres

    I write localhost:2772 get some idea blank page I am doing wrong ?

    • http://thejackalofjavascript.com/ Arvind Ravulavaru

      Thanks Andres, does you / route dispatch a page?