Back
4 min read

Authentication with Nodejs and JWTs - a simple example

When it comes to JWTs, most tutorials help you build a full implementation in Node. However, they rarely stop to show just the basics - just the esssential parts that need to be there for JWT authentication to work, and nothing more.

This article aims to give a simple, straightforward overview of the steps needed to add JWT auth to a Node app.

We'll start from an unprotected API with a super secret resource and go towards securing it with JSON Web Tokens (JWTs).

What we'll build

Our API will only contain two endpoints:

  • /login - will return the token
  • /super-secret-resource - the information that only logged in users should access

Step 1: Creating a Node API with Express

Let's start from the barebones - just an API that returns a very important resource, that should only be accessed by logged in users. This endpoint should return 401 Unauthorized if the user is not logged in. Since we don't have the /login endpoint yet, it will just always return 401 for now.

Create a new folder for the project, run npm init -y to initialize the project and run npm install express to install express.

Next, create a server.js file for the node app. In its simplest form, this is how the API would look:

// server.js
const express = require("express");
const app = express();

app.get("/super-secure-resource", (req, res) => {
  res
    .status(401)
    .json({ message: "You need to be logged in to access this resource" });
});

app.listen(3001, () => {
  console.log("API running on localhost:3001");
});

To start the API,  run node server.js in the console.

To check that the endpoint cannot be accessed, run curl:

curl -i localhost:3001/super-secure-resource

// Will output:
// 
// HTTP/1.1 401 Unauthorized
// X-Powered-By: Express
// Content-Type: application/json; charset=utf-8
// Content-Length: 62
// ETag: W/"3e-eJQzoUv1nk6AKpsmnnXBmFvKrI4"
// Date: Sat, 22 Jan 2022 09:05:53 GMT
// Connection: keep-alive
// Keep-Alive: timeout=5
// 
// {"message":"You need to be logged in to access this resource"}

The -i flag tells curl to also return the headers, so we can see the HTTP status code.

Notice the 401 HTTP status code - which stands for "Unauthorized" - and the message we returned from the endpoint.

Step 2: Return a JWT token on successful login

Next, we want to allow users to login and send back a verified token if their user and password are correct.

Since setting up a full database of users and password is out of the scope of this article, we'll just hardcode an admin/admin user and check for that as an example.

The first step is to install the npm jsonwebtoken module. This package will help us sign and verify JWT tokens.

npm install jsonwebtoken

Then, the endpoint will look like this:

const express = require("express");
const jsonwebtoken = require("jsonwebtoken");

// The secret should be an unguessable long string (you can use a password generator for this!)
const JWT_SECRET =
  "goK!pusp6ThEdURUtRenOwUhAsWUCLheBazl!uJLPlS8EbreWLdrupIwabRAsiBu";

const app = express();
app.use(express.json());

app.post("/login", (req, res) => {
  const { username, password } = req.body;
  console.log(`${username} is trying to login ..`);

  if (username === "admin" && password === "admin") {
    return res.json({
      token: jsonwebtoken.sign({ user: "admin" }, JWT_SECRET),
    });
  }

  return res
    .status(401)
    .json({ message: "The username and password your provided are invalid" });
});

Some things to note here:

  • the JWT_SECRET should be an unguessable long string
  • the part that actually does the signing is: jsonwebtoken.sign({ user: "admin" }, JWT_SECRET)

We can check a token is returned with curl:

curl -X POST http://localhost:3001/login --header "Content-Type: application/json" --data '{ "username": "admin", "password": "admin" }'

// {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE2NDI4NDQ0NTR9.O_710gS-6t9nmqvW0_E1GlP7MdoLMFR85GXUUeskNi8"}

You can decode the token and see what it contains on jwt.io

Some things to note:

  • The "Header" of the token contains the algorithm used - in our case, the algorithm for signing the token is HS256
  • You can input your secret in the bottom right to have jwt.io verify the token signature (if the signature is verified, it means the token hasn't been tampered with)
  • Besides the { user: admin } which we encoded, there's also another property there - iat, added by the jsonwebtoken library; this stands for "Issued at" - i.e. when the server created the token (as Unix timestamp)

Step 3: Send the token to the /super-secure-resource and verify it to allow access

Now let's use that token to allow the logged in user to access the restricted resource!

In order to do that, we will need to send the token to the API (as an Authorization header) and then verify the token on the server:

curl -i localhost:3001/super-secure-resource --Header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE2NDI4NDYzMzV9.gQj-qjoQwtvyU2XzER6yiT-T4DwYphjMft-kogW978c'

// Will return:
//
// HTTP/1.1 200 OK
// X-Powered-By: Express
// Content-Type: application/json; charset=utf-8
// Content-Length: 69
// ETag: W/"45-9rvFFDk8vkfruRd3Ok0vQNGIdMk"
// Date: Sat, 22 Jan 2022 10:15:03 GMT
// Connection: keep-alive
// Keep-Alive: timeout=5
//
// {"message":"Congrats! You can now accesss the super secret resource"}

Why this works:

  • By signing the token, you ensure that the server can check that whatever token it receives, was created by itself
  • If you try to tamper with the token, you'll invalidate it! The server will tell it's been tampered, so it will not authorise you!
  • Try it out: take the token, change it in jwt.io and try again -> it will fail!

Make it yours!

I specifically kept the source code for this article very barebones. Go ahead and improve it!

  • Add nodemon to automatically reload the server when you change the API
  • Add nodeenv and move the JWT_SECRET to as an environment variable
  • Setup an actual database with users and passwords
  • Add more endpoints and move the code that verifies the token in an express middleware

Know you should be immutably updating state in React,
but not sure what the best way is?

Get a handy reference of the most common state update operations and anti-patterns to watch out for 🕵🏻‍♀️


Drop in your email below to get a printable PDF with the cheatsheet. I'll also let you know whenever I publish a new blog post.

React Immutable State Updates Cheatsheet