How JSON web token works in NodeJS for Authentication with a Project

Suraj Vishwakarma - Aug 29 '22 - - Dev Community

Introduction

We create accounts and login into our accounts on various websites. The process of proving the
identity of a user can be called Authentication. It is one of the essential parts of any application to provide a better user experience and features to the user.

We have been using email/username with the combination of a password to verify the authenticity of a user. The process is not as simple as it looks as you have to pass data in encrypted form to avoid any data mishandling. You also need to store data in some form of encryption so that either owner or admin of the database can not have access to the password.

Today, we are going to implement an authentication system in React. It will have the following

  • Frontend in React: It will include the register and login form.
  • Backend in Express: It will manage the APIs to register and verify the user.
  • JWT: For managing the verification of the user with tokens.

So, let's get started.

What is JSON Web Token

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

In simple words, it is a method that is being used to transfer data securely between two parties in the form of a JSON Object. It takes data and performs hashing to convert it into an alphanumeric separated by two periods which is called a token. An example of the token is eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InN1cmFqb25kZXYiLCJpYXQiOjE2NjE3NTMzOTl9.U8upboa1MNwCc8vytnpbJkmuBFD3OgP37ovJZ0vlOvo. These tokens are provided to the user. Whenever the user, needs to be verified before displaying the page, these tokens act as a method of verification. These token in the backend is decoded and then verified.

A JWT token is formed as the method of signature. The signature is composed of the following:

  • Payload
  • Secret

Payload

It is the data that is needed to be stored in the token. This data is in the form of a JSON object. You can pass data such as username, name, location, and other data. This data is not encrypted and can be debugged with the token. So, avoid having a password in the payload.

An example of payload can be:

const payload = {
        "username": "surajondev"
        "job": "Content Writer"
    }
Enter fullscreen mode Exit fullscreen mode

Secret

A secret is an alphanumeric determined by the programmer. It is needed to create a unique token. This secret is highly confidential. The more complex secret the harder it becomes to decode the token.

You can store the secret in the environment variable and import it according to your need.

A secret can look like this:

SECRET="jafdsf45dsfdaskfamdsfdsa23"
Enter fullscreen mode Exit fullscreen mode

JWT Sign

Using payload and secret, we can now create a sign. This sign provides us with the token.

const token = jwt.sign(payload, process.env.SECRET)
Enter fullscreen mode Exit fullscreen mode

Project

Let's create a project using React ( You can use other frontend frameworks like NextJS ) and ExpressJS for the backend. Let's define the feature of our application:

  • Register User: We are going to take two inputs from the user - username and password. We will use the username to sign a token.
  • Display Image: We create a protected API path in our backend to send the Image URL and username to the frontend. This path will need a valid token, otherwise, it will send the error to the frontend.

The project structure is that we define the backend in the root folder. The fronted will goes into a sub-folder, client.

Frontend

For the front end, we are using React. Create a react app with the following command:

npx create-react-app jwt-auth
Enter fullscreen mode Exit fullscreen mode

Delete all the unnecessary files.

Now, it's time to install the necessary dependencies. Here is the list of all the dependencies:

  • Axios: It is a promise-based HTTP Client for node.js and the browser.

Here is the command to install

npm i axios
Enter fullscreen mode Exit fullscreen mode

Other than React-based dependencies, that is all we need to install.

App.js

In App.js, we have created a simple web page, to register a user. Users need to enter their username and password. After clicking Register, it will be passed to the backend through the API endpoint http://localhost:8000/register. After the valid register, frontend will have a JWT token. We will create the backend after frontend.

We also have a protected route in our backend. This will be visible to the user, who has registered, otherwise, it will give an error. The token in frontend is passed into the header with the name x-auth-token. With a valid request, it will display the username and an image.

We have used the useState hooks to store data from responses, emails, and passwords.

import './App.css';
import {useState} from 'react'
import axios from 'axios'

function App() {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [token, setToken] = useState('')
  const [data, setData] = useState() 
  const [error, setError] = useState() 
  const handleRegister = () => {
    axios.post('http://localhost:8000/register', {
      username,
      password
    })
    .then(res => {
      setToken(res.data)
      console.log(token)
    })
  }

  const handleSecret = () => {
    axios.post('http://localhost:8000/secret', {}, {
      headers:{
        'x-auth-token':token
      }
    })
    .then(res => {
      if(res.data.img){
        setData(res.data)
        setError()
      }else{
        setError(res.data)
      }
    })
  }

  return (
    <div className="App">
      <h1>Register User </h1>
      Username: <input type="text" onChange={(e)=> setUsername(e.target.value)}/>
      <br />
      Password: <input type="password" onChange={(e)=> setPassword(e.target.password)}/>
      <br />
      <button onClick={handleRegister}>Register</button>
      <br/>
      <h1>Protected Path</h1>
      <button onClick={handleSecret}>Image</button><br/>
      {
        data && 
        <div>
          <p>Hello {data.username}</p>
          <img height={200} src={data.img} alt="img" />
        </div>      
      }
      {
        error &&
        <p>{error.msg}</p>
      }
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Here is the web page.

App.js

Backend

We are going to use ExpressJS for our backend. So, return back to the root folder from the client folder. Let's create a basic package.json with the following command

npm init -y
Enter fullscreen mode Exit fullscreen mode

Now let's install the dependencies for our backend.

  • Express: As our backend in ExpresJS, we need to install the expresjs package.
  • Axios: As you know from fronted for API calls.
  • dotenv: For managing the environment variables. We will add our secret in env.
  • jsonwebtoken: Our main dependencies for creating and verifying JSON web tokens.
  • cors: It avoids any CORS error. If you don't know about CORS. You can read CORS error from Mozilla.

server.js

Now, let's create our server with the name server.js. At the top, we have imported all the necessary dependencies.

const express = require("express");
require('dotenv').config();
const jwt = require("jsonwebtoken");
const auth = require("./middleware/auth");
const cors = require('cors')
const app = express()

app.use(cors())
app.use(express.json({extended:false}))


app.post('/register', (req, res) => {
    const payload = {
        "username": req.body.username
    }
    const token = jwt.sign(payload, process.env.SECRET)

    res.send(token)
})

app.post('/secret',auth, (req, res) => {
    res.json({
        username: req.username,
        img:"https://i.pinimg.com/564x/78/a4/68/78a4688ee7138c6d9a278c15fab28663.jpg"
    })
})

const PORT = process.env.PORT || 8000

app.listen(PORT, ()=> console.log(`Server started at ${PORT}`))
Enter fullscreen mode Exit fullscreen mode

Let's discuss the two routes in our application.

/register

In this route, we are taking data from the request. The body contains the username and password. The payload for our JWT contains only the username in JSON object format. For token, we need to sign the payload with a secret. As for the secret, we are using the environment variable. Create a .env file and put your secret there.

SECRET="mysecret"
Enter fullscreen mode Exit fullscreen mode

Create a more complex secret for higher protection. We are using the jwt.sign() function for signing the payload with the secret.

const token = jwt.sign(payload, process.env.SECRET)
Enter fullscreen mode Exit fullscreen mode

After generating the token, I am sending it back to the frontend.

/secret

This route is a protected route with the middleware auth. Middleware is a function that has access to the requested data. It is used to process requests. We are going to use auth middleware to validate whether the requested user has a valid token. We will also decode the username from the token.

We are going to define middleware in another file.

middleware/auth.js

const jwt = require("jsonwebtoken");
require('dotenv').config();

module.exports = (req, res, next) => {
    const token = req.header('x-auth-token')

    if(!token){
        return res.json({msg:"No token found, authorization denied"})
    }

    try {
        const decoded = jwt.verify(token, process.env.SECRET)
        req.username = decoded.username
        next()
    } catch (error) {
        console.log(error)
        res.json({msg:"Token is not valid"})
    }
}
Enter fullscreen mode Exit fullscreen mode

At the top, our imports that is jasonwebtoken and dotenv. As the arguments, we have req, res, and next.

  • req: req will have all the data that is sent while calling to the endpoint.
  • res: res is for sending a response to the frontend.
  • next: next is being used to move forward to the endpoint, after processing the data.

We have three scenarios above:

  • No token: If the request does not contain a token or token in our format. It will send "No token found, authorization denied"
  • Invalid token: If the request has an invalid token in form of an expired or wrong token. It will send "Token is not valid".
  • Access granted: In the try block, we are verifying the user. For decoding the token, we need the token and secret. jwt.verify() function is used to verify the user. After decoding we are storing the information in the decoded variable. If any error occurs while decoding, an Invalid response will be sent to the frontend. After decoding, the decoded variables have the payload, that was inserted will generate the token with jwt.sign() function. We are setting the request with a variable username containing the username from the decoded token. If the user is valid, we are using the next() function to proceed with the request.

Demo

Now the project has been ready. Run your express server and react server then go to react server web page at http://localhost:3000.

Now register yourself by filling username and password. After successful registration. You will eligible for accessing the protected route. If you click on the Image button, you will see a Hello message with the provided username and an image of the JWT logo.

React - Server Running

If you click on the Image button without registering. You will get the error.

React - Server Error

I have created a GitHub Repository, where I put all the code related to the project. You can visit the repo through this link: https://github.com/surajondev/jwt-auth.

Extension

There is a lot of bugs and issue with the create project right now. Here are the following in detail:

  • No database: There is no database connection that makes us not able to store the password. That's why we haven't implemented the Login feature.
  • No request validation: We haven't implemented any request validation. If you leave the username and password, still it will provide you with a token.

If you want to do more fun, you can implement all the features in your application. Connect a database, I recommend MongoDB, to store username and password. For passwords, use encryption while storing in the database, you can use the bcrypt library for encryption.

Using all the features, you can also create the login page for validation of the user. For validating, you need to ensure the requested user already has an account and then match the encrypted password. Bcrypt has a function for verifying the password.

Have fun coding all the above features.

Connect With Me

Conclusion

Authentication in the web application is evolving and there is various method of integrating authentication in your application. There is authentication based on the crypto wallet. We will discuss and learn some methods of authentication in the later part of the series. I am going to create a series with the name Auth, where we learn about different methods of authentication.

You can follow me for the upcoming parts. Thanks for reading the blog post.

If you have any doubts or you want me to write an article to extend the project with validation and login features then let me know in the comment.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .