JWT Authentication - Beginners Guide With Real Application
JWT Authentication - Beginners Guide With Real Application

In this post you will learn in a super easy way what is JWT authentication and how we can implement it inside NodeJS.

What is it?

The first question is what is JWT and why do we need it at all.

JWT, or JSON Web Token is a standard to share security information between client and server.

So we use it to authenticate and login a user inside your web application. And you can use it both with mobile and web applications.

JWT Diagram

Here is how it works. As you can see we have 2 different things: client and server.

  • Client sends a login request to the backend. Typically it's /api/login where we provide username and password
  • Server checks our credentials, checks that this user exists in the database and gives back not only user information but also a JWT token. What is JWT? It's just a random string which contains some information.
  • We get on the client this JWT token in the response
  • After this we must save this information in a cookie or in local storage. So somewhere from where we can read it later
  • Later our client sends a request to the backend and will add this token to the headers of the request.
  • Our server gets this request, parses our token and understands to which user this token belongs.
  • Now server will deliver private information to the client

This is exactly how JWT token works but obviously it is super dry and it might not be that clear for you. This is why I prepared an example for you so we can implement a JWT token from scratch in an easiest way.

Prepared Project

I already prepared a small project with express server and mocked database. It doesn't really matter what database we use as JWT is something which is used after we get data from the database.

// server.js
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const { getArticles, login } = require("./db");
app.use(bodyParser.json());

app.get("/articles", (req, res) => {
  const articles = getArticles();
  res.send(articles);
});

app.post("/articles" () => {});

app.post("/login", (req, res) => {
  const user = login(req.body.username, req.body.password);
  if (!user) {
    return res.status(422).json({ error: "Incorrect email or password" });
  }
  res.send(user);
});

app.listen(3001, () => {
  console.log("server started");
});

Here we have an express server with several routes. For example we have /articles route which gets articles from our mocked database and responds with them. Also we have a POST to create an article. It is completely empty as we must implement JWT to be able to create an article.

Additionaly we get a /login request which used a login function from our database file and responds with users it user is found.

Here is how our database file looks like.

// db.js
const articles = [
  { id: 1, title: "Typescript Eslint - How to use them together", userId: 1 },
  { id: 2, title: "Prettier configuration", userId: 1 },
  { id: 3, title: "Gitlab vs Github vs Bitbucket", userId: 1 },
];

const users = [
  {
    id: 1,
    username: "foo",
  },
];

exports.getArticles = () => {
  return articles;
};

exports.createArticle = (title, user) => {
  const id = articles.length + 1;
  const article = {
    id,
    title,
    userId: user.id,
  };
  articles.push(article);
  return article;
};

exports.login = (username, password) => {
  const user = users.find((user) => user.username === username);

  if (!user || password !== "123") {
    return null;
  }

  return {
    id: user.id,
    username,
  };
};

exports.getUser = (id) => {
  return users.find((user) => user.id === id);
};

As you can see we have an array of articles and a single user. This is essentially our data just like in database. getArticles method simply returns the list of articles and login method checks the user and returns it back if it exists.

This is over simplified database but it works exactly the same with the real database.

Login authentication

As you can see we can make a login request and it checks our user and respond with some data or an error.

Adding JWT string

Now let's start with adding JWT authentication. The first step is to tune our login function. The main idea is that we must send back not only user information but also a JWT token. And for this we must update our login function.

const jwt = require("jsonwebtoken");

exports.login = (username, password) => {
  const user = users.find((user) => user.username === username);

  if (!user || password !== "123") {
    return null;
  }
  const token = jwt.sign({ id: user.id, username: user.username }, "foobarbaz");

  return {
    id: user.id,
    username,
    token,
  };
};

We installed a jsonwebtoken library which helps us to generate and verify JWT token. Now inside our login function we used jwt.sign which create a token for us. As a first parameter we can provide any data. It makes sense to save there at least an ID. As a second parameter we provide a secret string which only our backend knows. It this case only our backend can parse this JWT token next time.

As you can see we returned token property together with user data.

Token response

Now we get JWT token back in our request. It means that we implemented step 2 and 3 with creating a JWT secret and sending it back.

After we responded with the token our client must save somewhere this string. It can be local storage or cookie storage.

Adding middleware

Now we are coming to the interesting point. When our client sends correct token on the backend we must parse it, check it, get user information from it and respond with the correct data.

As an example we will implement POST request to create an article because only logged in user can create an article. As you saw previously our createArticle function in database file gets title and a full user. But this whole user doesn't come from the client. It must be gotten by backend with JWT token.

We could parse headers, token and get user directly in the request to get an article but it is not a best approach. Will we write the same code again and again at every place?

Must better way is to create a middleware which will do all this stuff and provide a user inside the request. For this let's create an authMiddleware.

// authMiddleware.js
const jwt = require("jsonwebtoken");
const { getUser } = require("./db");

module.exports = (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader) {
    return req.sendStatus(401);
  }

  const data = jwt.verify(authHeader, "foobarbaz");
  const user = getUser(data.id);

  if (!user) {
    return req.sendStatus(401);
  }

  req.user = user;

  next();
};

Middleware is just a function with req, res and next. It is similar to the route function but we get next here. We must call next to tell express that we are done with our logic and we can jump in route function.

Here we parse Authorization header first. It means that our client must provide a valid token inside an Authorization header. If our client doesn't do it then nothing will work.

If we didn't get a header we directly return unauthorized 401. After this we try to parse a token with jwt.verify. In order to do that as a second parameter we must provide the same string which we used when we created a JWT token.

The result of jwt.verify is the same data what we packed in the token. After this we typically want to get a user by the id of the token. If at some moment there is a problem we just respond with 401.

The last step is crucial. We provided the whole current user inside our req property. It allows us to read this user later inside our route function.

Using our middleware

Now we can implement our article creation.

app.post("/articles", authMiddleware, (req, res) => {
  const article = createArticle(req.body.title, req.user);
  res.send(article);
});

Here we provided as a second parameter authMiddleware. It means that we want to check the token and get current user for this request. It is only a request for logged in user.

Here we used createArticle function to create a new article and respond with it.

End result

As you can see in browser if we provide a correct token in Authorization header then we can create an article. This is exactly the implementation of JWT.

And actually if you are interested to learn how to build API with NestJS and JWT authentication for real project from start to the end make sure to check NestJS - Building an API for Real Project From Scratch.

📚 Source code of what we've done