Kickstarting your Node application — RESTful APIs, Authentication, Security — PART 2

Souvik Majumder
11 min readJul 31, 2021
Source: Unsplash

In the previous part, we have seen how to set the boilerplate for Node project. Let’s now see how do we authenticate our requests.

AUTHENTICATION

Let’s put some authentication so that only a valid user can access the API.

While there are various authentications mechanism, let us start with the simplest one and then we can gradually move on to the widely accepted ones.

Basic Authentication

Create an auth.js file in the root folder. We’re gonna fetch the credentials from the request headers.

Hereafter, if the user tries to access an API without providing the credentials, it should come up with an Unauthorized Error.

It should give us the response only on providing valid credentials.

Cool right ?!! But there is still a risk.

If I open the Developer Tools and navigate to the Networks tab, I can easily steal the generated token from there.

What if I share this token with few of my friends ? They can easily use this as a header in Postman and can get the data.

An Unforgivable Security Breach !!!!!

We need to find a way to make this token:-

  • Generated only after providing valid user credentials
  • One-time usable

Here comes our savior …………..

JWT (JSON Web Token)

As the name suggests, JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

Now I won’t go into much more theoretical details about JWT, but if you wanna read about JWT in details, you can find those from it’s official documentation here. Right now, we need to focus on why are we thinking of using JWT in our scenario.

Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. In short, the flow is like,

  1. User first needs to login with valid credentials using a Login API, in order to get a new token.
  2. He gets to see the token in the response header or body.
  3. He copies the token and use it as an Authorization Header in order to get the actual resource or data (let’s say getProducts) through the actual GET API.
  4. When the token gets expired, he needs to issue a new token while again repeating the above steps.

Let’s first begin by installing the jsonwebtoken npm package.

npm install --save jsonwebtoken

Let’s create a file named tokenvalidator.js in the root folder, which will validate the token every time the user tries to access the resource by hitting a GET or POST API in the browser or Postman.

The token validator JS file will use a secret and other details when it’ll try to run it’s validations. So, first we need to define these details as ENVIRONMENT Variables.

Let’s install dotenv npm package which will enable us to use the environment variables.

npm install --save dotenv

Import the dotenv module in your tokenvalidator.js file.

Create a .env file in the root folder and put these details given below.

Make sure you mention this .env file in your .gitignore file.

Now open the tokenvalidator.js file and paste the contents below.

The above code tells us that if the user doesn’t provide a token, it’ll throw a 403 No token provided error. However, if the user provides a token, it’ll try to validate the same by using the jwt.verify method. If the token matches, it proceeds ahead, else it will throw a 401 Unauthorized Access error.

Open the main index.js file and import the token validator file. Any API calls written after the token validator is imported, will first try to verify the token and only then it will provide the response.

Now try hitting a GET API (let’s say getProducts) in the POSTMAN, without providing any access token header.

Provide a random access token value in the header and try again

So, in order to pass a valid token in the header, we need to get it. And as I mentioned earlier, a new token is issues when the user logs in to the application providing valid credentials.

This means, that we have to first create a /login API endpoint.

Now, let’s copy this token from the response header and try putting it as a request header in the getProducts API and check whether we’re getting the data or not.

It works !!!

In real time scenario, there has to be a login page server-side or client-side rendered, where the users will be able to put the credentials and login.

Let me show you that too. I’ll be creating a login page which would be server-side rendered from our Node server.

npm install ejs

The above command will enable you to use HTML pages in your NodeJS application.

Create a login.html file inside the root folder.

Go to the main index.js and update the app.get(‘/’) endpoint. Make sure you install the path npm module.

Paste the below code inside login.html

Login Page

Now when we try logging in by putting valid credentials, we should be able to see the message below.

Alternatively, we can also create a front-end application and check the same. I have created a sample React application with a Login component.

Let me also bring up the Developer tools and navigate to the Network Tab.

We can see that the login POST API was called. JWT has signed and a new token along with a refresh token got set in the response headers, as shown above.

Although we can manually copy this token above and paste it in the headers form in the Postman App while trying to execute the getProducts API, in real time scenario, where the front-end app tries to hit the same API, the token needs to be set or stored somewhere in the browser.

While there are various options for storing tokens such as localStorage, sessionStorage and Cookies, I prefer using Cookies more, since the other two are susceptible to cross-site scripting (XSS) attacks.

If you set the JWT on cookie, the browser will automatically send the token along with the URL for the Same Site Request.

For enabling this, let’s first install the cookie-parser npm module.

npm install cookie-parser

Open the main index.js file again and inside the /login POST API, make the below changes.

login post API

Now let’s try logging in again and check the Networks Tab.

The token and refresh token don’t exist directly in headers anymore. Instead those two got set as cookie. We can see the same if we navigate to the Application tab.

So inside our tokenvalidator.js file, we can retrieve the signed token from the cookies, instead of the headers.

From now on, whenever we try to hit the /getProducts API, in the URL tab, it will automatically fetch the token from the browser cookie and show us the output on validation.

Securing Cookies

Encrypting the token using Crypto —

Instead of directly passing the tokens to the front-end, we’re gonna encrypt it first in the node end and then pass it to the front-end.

For doing this, we will need to install the crypto npm module.

npm install crypto

Next, create a folder named utils inside the root of your node project. Create a file named CryptoFunctions.js

and paste the content below.

Open the .env environment file and put the secret key as shown below.

So basically encrypt and decrypt are two helper functions that are going do the encryption and decryption jobs for us.

Open the main index.js file and import the two helper functions

const { encrypt, decrypt } = require('./utils/CryptoFunctions')

Inside the /login POST API, just wrap the encrypt method around the signed tokens.

const token = encrypt(jwt.sign(user, process.env.SECRET, { expiresIn: parseInt(process.env.TOKENLIFE)}));const refreshToken = encrypt(jwt.sign(user, process.env.REFRESHTOKENSECRET, { expiresIn: parseInt(process.env.REFRESHTOKENLIFE)}));

From now onwards, whenever successful login happens, the tokens are first encrypted and then set in the cookies.

From the front-end app, whenever you make a GET or POST request, just follow these steps.

  • Fetch the token from the cookies using document.cookie
  • Wrap the decrypt method around the retrieved tokens and then set it as the request header. Make sure in the front-end project root, you create another CryptoFunctions.js file inside an util folder, just like we did at the node end.

Enabling CSRF protection —

Cookies are susceptible to Cross-Site Request Forgery attacks (CSRF) attacks.

A CSRF attack is one in which a user is fooled into performing some action in an app that they are currently logged into. If an attacker is able to get the user to make a request to that app unknowingly, the browser will automatically send its cookies to the attacker.

A simple example can be an instance where the user is misguided to click an image which has some parameters running in the background. As soon as the user clicks on it, a POST operation occurs which transfers all the cookies to the hacker’s side.

CSRF protections are therefore usually applied with POST requests, because the attacker would need the user to send details including the cookies and tokens. Therefore, on applying CSRF protection, the CSRF token will be applied on the header of every POST request. This means that only on passing valid CSRF token from a trusted front-end, should fetch the data from the backend. In the hacker’s case, the POST request that he is running behind that suspicious image will contain wrong or no CSRF token, as a result the data cannot be fetched from the backend.

CSRF tokens should be generated on the server-side. They can be generated once per user session or for each request.

In order to do CSRF protection we need to use an anti-CSRF token and for that we need to install a library called csurf.

npm install csurf

For the Node app to use CSRF Protection, we need to update the main index.js as below.

Hereafter, whenever we will try to login (which also uses a POST method), we will get the below error.

The reason is we’re not passing the CSRF token.

So, in the Login component, we have to retrieve a new CSRF token every time when the user tries to log in. In other words, in real time projects, this step should be implemented in the front-end app (React or Angular).

I am doing this inside the main App component in the React side.

As you can see above, whenever the front-end application loads, the CSRF token is retrieved and stored as a props which will be available globally across all the components and can be accessed any time. We can also use the Context API of React for the same purpose.

Therefore, from now on, whenever I make a POST request specifically, I need to set the X-CSRF-Token header. Let’s take the Login component for example.

Cookies Flags —

There are some important flags for cookies that we need to take care of.

  • HttpOnly — A cookie with the HttpOnly attribute is inaccessible to the JavaScript Document.cookie API; it is sent only to the server. For example, cookies that persist server-side sessions don't need to be available to JavaScript, and should have the HttpOnly attribute. This precaution helps mitigate cross-site scripting (XSS) attacks.

As you can see above the document.cookie API only gave us the _csrf (not used in our situation), although we had set the other two tokens as cookies.

  • Secure — A cookie with the Secure attribute is sent to the server only with an encrypted request over the HTTPS protocol, never with unsecured HTTP (except on localhost), and therefore can't easily be accessed by a man-in-the-middle (MITM) attacker. MITM attack happens at Internet cafes, by listening to the WiFi. More malicious forms are malware and hijacked proxy servers. Insecure sites (with http: in the URL) can't set cookies with the Secure attribute.
  • SameSite — The SameSite attribute lets servers specify whether/when cookies are sent with cross-site requests (where Site is defined by the registrable domain), which provides some protection against cross-site request forgery attacks (CSRF).
    It takes three possible values: Strict, Lax, and None. With Strict, the cookie is sent only to the same site as the one that originated it

To enable the above flags, go to your main index.js file in the node application and update the login API as shown below.

Done and dusted !!

Conclusion

So, we have created a boilerplate for our Node application. We have learned how to make use of JWT tokens and how to secure them from CSRF attacks.

In the next article, let’s have a wonderful tour of another wonderful warrior, the OAuth.

Until then, Happy Coding !! :)

--

--

Souvik Majumder

Full Stack Developer | Machine Learning | AI | NLP | AWS | SAP