How to Implement JWT (JSON Web Tokens) Authentication in TypeScript
Guides5 min read
Start Chatting

How to Implement JWT (JSON Web Tokens) Authentication in TypeScript

When you create an application where users can log in, you need a smart way for your server to remember who they are. In the old days, websites used things called session cookies. The server had to save a unique random text string inside its main database for every single user who logged in. Then, every single time that user clicked a button or loaded a new page, the server had to talk to the database to check if the session token was still valid. This worked, but it made databases super tired and slow when thousands of people logged in at the exact same time.

Today, developers prefer using JSON Web Tokens—or JWT for short. Think of a JWT like a digital theme park ticket that the server prints and hands to your browser when you type the correct password. The ticket has your user ID stamped on it, an expiration date, and a special cryptographic signature at the bottom. Because the signature is impossible to forge, your server can just look at the ticket and instantly know it is genuine without checking any databases. Let's write a clean, easy-to-understand setup to create and read these tokens using TypeScript.

The Three Pieces of a Token

If you look at a raw JWT string, it looks like a massive, unreadable mess of random letters split up by two periods. It looks like this: aaaaa.bbbbb.ccccc. It is split into three distinct sections:

  1. The Header: This is the first block. It is just a tiny piece of public text that tells the system what kind of token it is and what math formula was used to sign it.
  2. The Payload: This is the middle block. This contains the actual information you want to share, like your user ID or your email address. Anyone can decode this and read it, so never put passwords or sensitive credit card details here!
  3. The Signature: This is the final block at the end. It takes the header, the payload, and a secret password known only to your backend server, and runs them through a hash algorithm. If a user tries to alter their user ID in the payload block, the signature will no longer match, and the server will instantly catch the fraud.

Coding the Token Generator

To follow along with this code block, make sure you install the required library and types inside your terminal first by running npm install jsonwebtoken @types/jsonwebtoken.

Let's write a file named tokenHelper.ts to handle creating our secure digital tickets when a user logs into our platform.

import jwt from 'jsonwebtoken';

// This acts as a blueprint telling TypeScript what info lives inside our ticket
interface UserTicketInfo {
  userId: string;
  accountType: string;
}

// This is the top-secret master key your server uses to sign tickets
// In real life, load this safely from your hidden environment configurations
const MY_SERVER_SECRET_KEY = "super_secret_zudisa_developer_key_dont_leak";

export function createNewUserToken(userData: UserTicketInfo): string {
  // We package the user data inside the token and make it expire in 2 days
  const token = jwt.sign(
    { id: userData.userId, role: userData.accountType },
    MY_SERVER_SECRET_KEY,
    { expiresIn: '2d' }
  );
  
  return token;
}

Checking the Ticket on Private Pages

Once your user has their token token, their browser will send it back to the server every time they try to load private areas, like a chat room layout or a settings menu. We need to write a check stop—called a middleware function—that intercepts the request, grabs the ticket, and verifies its authenticity before letting the user pass.

Create a file named authMiddleware.ts and paste this clean logic:

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

const MY_SERVER_SECRET_KEY = "super_secret_zudisa_developer_key_dont_leak";

export function checkUserTicket(req: Request, res: Response, next: NextFunction) {
  // 1. Grab the ticket from the incoming request header
  const totalHeaderString = req.headers['authorization'];
  
  // Tokens are usually sent as "Bearer <token_string>", so we split the text to get just the token
  const tokenString = totalHeaderString && totalHeaderString.split(' ')[1];

  // 2. If there is no ticket, block them immediately
  if (!tokenString) {
    return res.status(401).json({ error: "Access Denied: You don't have a valid ticket." });
  }

  try {
    // 3. Run the math formula to verify the signature matches our secret server key
    const decryptedPayload = jwt.verify(tokenString, MY_SERVER_SECRET_KEY);
    
    // Attach the decrypted data right onto the request object so our routes can use it easily
    (req as any).verifiedUser = decryptedPayload;
    
    // 4. Everything looks perfect! Let them through to the page logic
    next();
    
  } catch (error) {
    // If the ticket was tampered with or expired, throw them out
    return res.status(403).json({ error: "Authentication Failed: Your ticket is broken or expired." });
  }
}

Connecting the Logic to Your Routes

Now, you can protect any page on your app by simply sliding that check function right into your routing paths.

Here is how you use it inside a standard Express API backend server layout:

import express from 'express';
import { checkUserTicket } from './authMiddleware';

const app = express();

// A public page that anyone on the web can look at
app.get('/all-posts', (req, res) => {
  res.send("This is public data visible to everyone.");
});

// A private profile page protected by our new ticket validation checkpoint
app.get('/my-private-profile', checkUserTicket, (req, res) => {
  // Grab the data we attached inside our validation check step
  const activeUser = (req as any).verifiedUser;
  
  res.json({
    message: "Welcome to your private vault!",
    yourUserId: activeUser.id,
    yourPrivileges: activeUser.role
  });
});

This pattern keeps your app incredibly secure. Instead of forcing your system to look up a user profile inside a heavy database a million times an hour, your backend can just run a split-second math signature check on the incoming token and move on. It saves computing power, stops hackers from forging fake user sessions, and scales perfectly as your platform grows.