DEV Community

Cover image for UDERSTANDING JWT -JSONWEBTOKEN
Evans Ansong
Evans Ansong

Posted on

UDERSTANDING JWT -JSONWEBTOKEN

In token-based authentication, we store the user’s state on the client. JSON Web Token (JWT) is an open standard (RFC 7519) that defines a way of securely transmitting information between a client and a server as a JSON object. In this blog, I will use tokens and JWT terms to demonstrate how the transmission between the client and the server actually works.

ANATOMY OF JWT

The anatomy of a JWT  contains three forms that is the Header , Payload and the Signature . The headers tells the signing algorithm of the token, payload is the data that the JWT contains which can be the users profile and other information and the signature part verifies the authenticity of the parts. The pieces of data are referred to us claims.

1_u3a-5xZDeudKrFGcxHzLew.png

Example of JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Enter fullscreen mode Exit fullscreen mode

The above example uses the Base64 URL-encoded JSON object. It contains information describing the type of token and the signing algorithm used, such as HMAC, SHA256, or RSA.

Example

{
   "alg": “RSA”,
   "typ": "JWT"
}

Enter fullscreen mode Exit fullscreen mode

The JWT Payload contains claims that are pieces of information asserted about a subject. The claims will contain the statements about the user and any other additional information. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature. Claims will either be registered, public or private.

Example

{
   “Username”: “Jackman”,
   “Email”: "Jackman@example.com”,
   "admin": true
}
Enter fullscreen mode Exit fullscreen mode

 Creating the JWT signature involves taking the encoded header, payload, the signing using the signing algorithm specified and a secret key.

Example

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Enter fullscreen mode Exit fullscreen mode

The above will create a JWT token using the HMAC SHA256 algorithm. This is the signature to verify that the token has not been modified when the secret key is applied on the back-end. Meaning anyone who have the token can see the data of the token unless encrypted but they cannot modify the data in the token unless they have the secrete key which should not be possible since the server is the only thing that have access to the secrete key.

HOW JWT IS USED

  • The user logs in ( they send their credentials to the server and the server will check if it's correct and logs in)
  • Upon the request, the server verifies the credentials and generates a JWT token containing the user information and sends it back to the client (front-end)
  • The front-end then stores the JWT (usually in cookies, sessions or local storage) and includes this JWT whenever it needs privilege access to.
  • The server then uses the JWT to verify the validity of the token that the client sent is legit and has not been modified.

But before the front-end send a JWT to the server ,it will look like this in the request header {authentication:'Bearer ’}

SIGNING VRS ENCYPTING TOKENS

Signing proves that the data in the JWT has not been modified, where as encrypting is like preventing the data in the JWT from being seen by third parties. JWT are not encrypted by default.

ADVANTAGES OF JWT (Token-based authentication)

  • Token based authentication is stateless (all the information is inside the token itself)

  • They also uses JSON(JavaScript object notation) which is compact and secure compared to others like xml.

  • The data is stored in the JWT, meaning it can contain any type of data giving the flexibility on what information is to be included in the token.

  • And Tokens remain valid until they expire or until the data has been modified.

  • Logout is also simple by destroying the token in the browser without server interaction.

NOTE:The user will have to re-authenticate the tokens when they expire so they have to login every two days or something depending on the expiration date set on the token.

LIMITATIONS

  • Storing a lot of data in the token makes it huge, which slows the requests.

  • Token in the client-side might be hijacked by an attacker making it vulnerable to Cross-site scripting (XSS) attacks which often occurs when an outside entity can execute code within your domain by embedding malicious JavaScript on the page to read and compromise the contents of the browser storage.

DEMO

To carry on with this Demo, you must have NodeJs, MongoDB installed on your system, you can install the latest MongoDB version here, latest NodeJs version here,you must also know how to use basic terminal commands and have postman account, you can sign up here.

We will build a simple RESTful API to use JWT to understand how all this works. To begin you need to get the starter template on my GitHub here. Follow the instructions in the README.md to get the server up and running and come back to this demo.

There are a few packages we will need to install :

  1. Bcrypt to encrypt the user password.
  2. dotenv to configure the environment variables and,
  3. Jsonwebtokens

So open the project and install the packages we will need , in the project directory (outside the src directory) run this command :

npm install  bcrypt  dotenv jsonwebtoken
Enter fullscreen mode Exit fullscreen mode

After these are installed , we will have to configure the signup route to be able to sign up users and get the user information:

Screen Shot 2021-12-22 at 10.53.01 AM.png
src -> routes/ :

touch signUpRoute.js

Enter fullscreen mode Exit fullscreen mode

Good! now let’s import the packages and add the following code to the signUpRoute.js file :

import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { getDbConnection } from '../db';


export const signUpRoute = {
    path: '/api/signup',
    method: 'post',
    handler: async (req, res) => {
        const { email, password } = req.body;
        // this is the name of the database 
       //which  will be created by default 
        const db = getDbConnection('jwt-demo-db');
        // checking to see if the user already exists in the db
        const user = await db.collection('users').findOne({ email });
        if (user) {
            res.sendStatus(409);
        };
      //if no user exist in the database with such credentials, 
      //we hash the password and save the user to the db 
        const passwordHash = await bcrypt.hash(password, 10);
        const personalInfo= {
            hairColor: 'brown',
            favoriteFood: 'rice ',
            bio: 'I am the father of all',
        };
        const result = await db.collection('users').insertOne({
            email,
            passwordHash,
            info: personalInfo,
            isAdmin: false,
        });
        const { insertedId } = result;
 // code here 
    }
};
Enter fullscreen mode Exit fullscreen mode

With this in place, we can now generate json web token for the information that will be send back to the client so they can store and use it. We need to configure the JWT environment variable, so in the top level folder outside the src folder , run this command :

touch .env 
Enter fullscreen mode Exit fullscreen mode

This will create a new file for us so we can insert out secret key:

JWT_SECRET=thywillbedone
Enter fullscreen mode Exit fullscreen mode

NOTE : you can change the secrete key to your own

Now we can generate json web token by adding this code just below the inserted id on the signUpRoute.js :

  jwt.sign({
            id: insertedId,
            email,
            info: personalInfo,
            isAdmin: false,
        },
        process.env.JWT_SECRET,
        {
            expiresIn: '2d',
        },
        (err, token) => {
            if (err) {
                return res.status(500).send(err);
            }
            res.status(200).json({ token });
        });
Enter fullscreen mode Exit fullscreen mode

Making the complete signUpRoute.js file look like this :

import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { getDbConnection } from "../db";

export const signUpRoute = {
  path: "/api/signup",
  method: "post",
  handler: async (req, res) => {
    const { email, password } = req.body;
    // this is the name of the database which  
    // will be created by default
    const db = getDbConnection("jwt-demo-db");
    // checking to see if the user already exists
    const user = await db.collection("users").findOne({ email });
    if (user) {
      res.sendStatus(409);
    }
    //if no user exist in the database with such credentials,
    //we hash the password and save the user to the db
    const passwordHash = await bcrypt.hash(password, 10);
    const personalInfo = {
      hairColor: "brown",
      favoriteFood: "rice ",
      bio: "I am the father of all",
    };
    const result = await db.collection("users").insertOne({
      email,
      passwordHash,
      info: personalInfo,
      isVerified: false,
    });
    const { insertedId } = result;

    jwt.sign(
      {
        id: insertedId,
        email,
        info: personalInfo,
        isAdmin: false,
      },
      process.env.JWT_SECRET,
      {
        expiresIn: "2d",
      },
      (err, token) => {
        if (err) {
          return res.status(500).send(err);
        }
        res.status(200).json({ token });
      }
    );
  },
};
Enter fullscreen mode Exit fullscreen mode

Great!!!, but we need to add the route to the index file to be able to make it work :

src -> routes -> index.js and add :

import { signUpRoute } from './signUpRoute';

Enter fullscreen mode Exit fullscreen mode

Now add signUpRoute to the routes array :

import { testRoute } from './testRoute';
import { signUpRoute } from './signUpRoute';

export const routes = [
    testRoute,
    signUpRoute,
];
Enter fullscreen mode Exit fullscreen mode

Don't worry too much about the structures of the routes unless you are curious 👀 It is also another efficient way of structuring our app

Now we can start our server and test our route with postman by sending a POST request to :

http://localhost:8080/api/signup

Firstly we need to make the server pay attention to the environment variable so let's add ( -r dotenv/config) by editting the package.json file scripts section to this:

  "scripts": {
    "start": "nodemon --exec babel-node -r dotenv/config ./src/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
Enter fullscreen mode Exit fullscreen mode

So the package.json file should also look like this :

{
  "name": "back-end",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon --exec babel-node -r dotenv/config ./src/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/core": "7.13.10",
    "@babel/node": "7.13.12",
    "@babel/preset-env": "7.13.12",
    "bcrypt": "^5.0.1",
    "dotenv": "^10.0.0",
    "express": "4.17.1",
    "jsonwebtoken": "^8.5.1",
    "mongodb": "3.6.5",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "nodemon": "2.0.7"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now start the server and set the accept parameters in postman to be: raw - json

Screen Shot 2021-12-22 at 11.11.46 AM.png

The data we are expecting will look like this, so add this credentials to the form data:

{
    "email":"someone@gmail.com",
    "password":"noonecanstopit323"
}

Enter fullscreen mode Exit fullscreen mode

Screen Shot 2021-12-22 at 11.14.12 AM.png

If configuration was successful and you click send, you should get a response like this :

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwZGNkMzI4MzQwMzQ3NzcxZWVhNmUxMyIsImVtYWlsIjoiZG90QGdtYWlsLmNvbSIsImluZm8iOnsiaGFpckNvbG9yIjoiIiwiZmF2b3JpdGVGb29kIjoiIiwiYmlvIjoiIn0sImlzVmVyaWZpZWQiOmZhbHNlLCJpYXQiOjE2MjUwODQ3MTMsImV4cCI6MTYyNTI1NzUxM30.Hadj4ReKFOj46-H7ua7a6yPAhvE8eenLQ9KNwgzfOcE"
}

What we get back is the token which contains all the data for the new user.

To verify this we can use the  jwt.io website to parse the JWT token information. You can use jwt.io to experiment with JSON Web Tokens by decoding and encoding them.

So copy the token and paste it in the box and you should see the token decoded!! containing the exact information we signed with jwt.

Screen Shot 2021-12-22 at 11.35.09 AM.png

Screen Shot 2021-12-22 at 11.59.05 AM.png

This is how the information will be stored on the client side when the user signs in so they can access it and use it however.

We can even verify from the mongo shell that the newly created user was saved to the database.

Screen Shot 2021-12-23 at 12.16.30 PM.png

SUMMARY

Authentication tokens and 2FA play a key role in establishing zero-trust network access control. For example a server could generate a token that has the claim "logged in as administrator" and provide that to a client. The client could then use that token to prove that it is logged in as admin.

Thanks a lot for coming through! I wish you a merry Christmas and a happy new year in advance! Happy coding.

Top comments (0)