JWT Authentication

JWT Authentication

ยท

6 min read

What is authentication?

Authentication is the process of verifying the identity of an individual, system, or device to ensure that they are who they claim to be. It is a fundamental concept in information security and is used to protect sensitive data, systems, and resources from unauthorized access. Authentication protocols and mechanisms are implemented in various systems, including computer networks, websites, online services, and mobile applications. These systems use authentication to control access to sensitive information, protect user accounts, and prevent unauthorized use or malicious activities.

What is JWT?

In the digital world, a JWT is like that special stamp. It's a small piece of information that contains some important details about you. When you log in to a website or app, the server gives you this JWT, which you can think of as your digital stamp. It's unique to you and is specific to that particular website or app.

A JWT consists of three parts: a header, a payload, and a signature, separated by periods (e.g., xxxxx.yyyyy.zzzzz).

  1. Header: The header typically contains information about the type of token (JWT) and the signing algorithm used.

  2. Payload: The payload contains the claims or statements about the user or entity being authenticated. These claims can include information such as the user's identity, roles, permissions, and additional metadata. The payload is encoded as a JSON object.

  3. Signature: The signature is created by combining the encoded header, the encoded payload, and a secret key known only to the server. The signature is used to verify the integrity of the token and ensure that it has not been tampered with.

When a user logs in or authenticates with a server, the server generates a JWT and sends it back to the client. The client then includes the JWT in subsequent requests which when received by the server, is used to verify the user from the database.

Steps of basic jwt auth

  1. User signs in to the website which requests the server

  2. The server returns a jwt token to the user which will then be stored in browser cookies.

  3. Now whenever the user again makes the request the token will be passed to the server and the server will verify whether the user with that token id exists or not.

  4. If it exists the server will send a confirmation as to whether provide user access to the website or not.

Code -

Below is our normal express js server setup, we also need a database to store our users whenever they sign up for our service here we are using MongoDB atlas.

const express = require("express");
const app = express();
const cors = require('cors');
const mongoose = require("mongoose");

// this user import contain our singup and login routes
const user = require("./model/controller");
require('dotenv').config();

const port = process.env.PORT || 3000;
const url = process.env.URL;

app.use(cors());
app.use(express.json());
app.use(user);

app.listen(port,()=>{
    console.log("server listening on "+port);

    mongoose.connect(url).then(()=>{
        console.log("connected to db");
    })
})

app.get("/",(req,res)=>{
    res.status(200).json("hi from server");
})

Below is our user schema model code which we will use to save our user to the database. We have requested three things from the user - name, email, and password.

const mongoose = require("mongoose");

const userSchema = mongoose.Schema({
    name : {
        type : String,
    },
    email : {
        type : String,
    },
    password : {
        type : String,
    }
})

const user = mongoose.model('user',userSchema);
module.exports = user;

Below is our controller code which will take care of our authentication routes.

const user = require('./user');
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');

// sign up route
router.post("/signup",async(req,res)=>{
    const info = new user({
        name : req.body.name,
        email : req.body.email,
        password : req.body.password
    })
    await info.save().then((d)=>{
        const token = jwt.sign({
            id : d._id
        },"secret-elite016");
        res.status(200).json(token);
    })
})

// log in route discussed later
router.post("/login",async(req,res)=>{
    const name = req.body.name;
    const token = req.body.token;
    const userid = jwt.verify(token, "secret-elite016");
    user.findById(userid.id).then((d)=>{
        res.status(200).json(d);
    }).catch((err)=>{
        console.log(err);
    })
})

module.exports = router;

We have our signup route which takes the name, email, and password from the user and then saves it to the database after then it uses the jsonwebtoken npm package.

await info.save().then((d)=>{
        const token = jwt.sign({
            id : d._id
        },"secret-elite016");
        res.status(200).json(token);
    })

This below piece of code generates a token by receiving the initial argument as our payload and d._id here is the user id generated in our database after saving it, the second argument is our secret key which can be random to any string. After the token is made we send back this token to the user using res.status(200).json(token). The output looks something like this when the user calls this API endpoint from the frontend. Here we are using thunder client a vs code extension to make requests.

Now as our user has received the token we will save this token in the browser cookies. It can be simply done by using any 3rd party packages or simply running the command in our frontend while hitting the above endpoint.

window.cookieStore.set("token",token);

Let's say now our user is logging in to our web service and we want to fetch all of his account info from the database. We have added a login route that will demand a username and token.

router.post("/login",async(req,res)=>{
    const name = req.body.name;
    const token = req.body.token;
    const userid = jwt.verify(token, "secret-elite016");
    user.findById(userid.id).then((d)=>{
        res.status(200).json(d);
    }).catch((err)=>{
        console.log(err);
    })
})

The token will be read from the request body and then we will run the

jwt.verify(token, secretOrPublicKey, [options, callback]) code to get back our user-id and then it will be used to run a query to find user information from the database and send back it as a response to the frontend. The output looks something like the below -


Note -

One major flaw in this is that we can't pass sensitive information as in our token we have passed the user's id which is a bad practice as it can be decoded easily with the help of online websites. For example, try using putting the jwt token in this website below.

Website

As we can see it decoded our true user id present in our database.

To prevent this we need to use an encryption and decryption package to encrypt our user id first then from the encrypted one we can make the token and send it to the user so that it can be saved to the browser. Now when users access the login route the encrypted token will be passed to the server which then after decrypting it and verifying the jwt command we will get our original user id back to search through our database. One such package we can use is crypto-js. The link is -

crypto-js package

It has multiple types of encryption one can explore through by reading the documentation. We can add the following code to our previous code to encrypt and decrypt.

const cryptojs = require("crypto-js");

// plain text encryption
const encryptedId = cryptojs..AES.encrypt('my message', 'secret key 123').toString();

// decryption
const decryptedId  = cryptojs.AES.decrypt(token, 'secret key 123');

This is the basic wrap-up of jwt authentication.


Follow -

Comment below for suggestions and improvement :) Follow me for more such content ๐Ÿ˜€. If you have any doubts you can message me, and I will respond asap :)

ย