How to Implement Secure User Authentication Using JWT, Express, and TypeScript

Narsi BhatiNarsi Bhati
4 min read

In modern web applications, handling user authentication securely is crucial. One of the most popular and effective ways to authenticate users is by using JSON Web Tokens (JWT). In this post, we'll walk through the steps to implement JWT-based authentication using Express and TypeScript, covering everything from sign-up to token validation and securing routes.

Prerequisites

Before you begin, ensure that you have the following installed:

  • Node.js and npm

  • TypeScript

  • Express

  • JWT (jsonwebtoken)

You can install Express and JWT by running the following commands:

bun add express jsonwebtoken

You'll also need TypeScript typings for Express:

npm add -d @types/express

Project Structure

Here’s how we’ll structure our project:

/src
  |-- /middleware
    |-- inputValidation.ts
    |-- authorization.ts
  |-- /types
    |-- AuthRequest.ts
  |-- enum.ts
  |-- index.ts

1. Setting Up Express

First, let's set up our Express server and define routes for sign-up, sign-in, and accessing a protected route. This will form the core of our authentication flow.

import express from "express";
import jwt from "jsonwebtoken";
import { Status } from "./enum";
import { inputValidation } from "./middleware/inputValidation";
import { authorization } from "./middleware/authorization";
import { AuthRequest } from "./types/AuthRequest";

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

const JWT_SECRET = "randomMeSecret"; // Secret for signing JWT tokens
const users = new Map<string, string>(); // In-memory store for users

app.get("/", (_req, res) => {
  res.status(Status.Ok).json({ success: true, message: "server [ on ]" });
});

app.post("/signup", inputValidation, (req, res) => {
  const { username, password } = req.body;
  if (!users.has(username)) {
    users.set(username, password);
    res.status(Status.Ok).json({ success: true, message: "user created" });
  } else {
    res.status(Status.Conflict).json({ success: false, message: "username already exists" });
  }
});

app.post("/signin", inputValidation, (req, res) => {
  const { username, password } = req.body;
  if (!users.has(username)) {
    res.status(Status.NotFound).json({ success: false, message: "User not available" });
  } else {
    const userPassword = users.get(username);
    if (userPassword === password) {
      const token = jwt.sign({ username }, JWT_SECRET, { expiresIn: "30s" });
      res.status(Status.Ok).json({ success: true, token });
    } else {
      res.status(Status.Unauthorized).json({ success: false, message: "invalid password" });
    }
  }
});

app.get("/me", authorization, (req: AuthRequest, res) => {
  res.status(Status.Ok).json({ success: true, username: req.user });
});

const server = app.listen(3000, () => {
  console.log("server : [ ON ]");
});

process.on("SIGINT", () => {
  server.close(() => {
    console.log("server : [ OFF ]");
    process.exit(0);
  });
});

2. Validating User Input with Middleware

Here’s the code for inputValidation.ts:

import { type Request, type Response, type NextFunction } from "express";
import { Status } from "../enum";

export const inputValidation = (req: Request, res: Response, next: NextFunction): void => {
  const { username, password } = req.body;

  if (!username || !password) {
    res.status(Status.BadRequest).json({ success: false, message: "wrong input" });
    return;
  }
  next();
};

3. Authorization Middleware

Here’s the code for authorization.ts:

import { type Request, type Response, type NextFunction } from "express";
import { Status } from "../enum";
import jwt from "jsonwebtoken";
import { type AuthRequest } from "../types/AuthRequest";

const JWT_SECRET = "randomMeSecret";

export const authorization = (req: AuthRequest, res: Response, next: NextFunction): void => {
  const token = req.headers.token;

  if (!token || typeof token !== "string") {
    res.status(Status.Unauthorized).json({
      success: false,
      message: "Unauthorized - invalid or missing token",
    });
    return;
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    if (typeof decoded !== "object" || !decoded || !("username" in decoded)) {
      res.status(Status.Unauthorized).json({ success: false, message: "Unauthorized - invalid token" });
      return;
    }

    req.user = (decoded as { username: string }).username;
    next();
  } catch (e) {
    res.status(Status.Unauthorized).json({ success: false, message: "Unauthorized - token not valid" });
  }
};

4. Creating Custom Request Types

Here’s the code for AuthRequest.ts:

import { type Request } from "express";

export interface AuthRequest extends Request {
  user?: string;
}

5. HTTP Status Codes Enum

Here’s the code for enum.ts:

export enum Status {
  Ok = 200,
  Created = 201,
  NoContent = 204,
  BadRequest = 400,
  Unauthorized = 401,
  Forbidden = 403,
  NotFound = 404,
  Conflict = 409,
  UnprocessableEntity = 422,
  ServerError = 500,
  ServiceUnavailable = 503,
}

Conclusion

With this setup, you have a basic yet secure authentication system using JWT with Express and TypeScript. We’ve covered:

  • Middleware for input validation, token verification, and authorization.

  • TypeScript’s type safety for request data.

  • An enum for clearer HTTP status codes.

0
Subscribe to my newsletter

Read articles from Narsi Bhati directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Narsi Bhati
Narsi Bhati

Highly motivated and results-driven Software Engineer with hands-on experience in full-stack development, including expertise in languages such as Go, TypeScript, Proficient in technologies like React, Node.js, Next.js, Docker, and cloud platforms such as Amazon Web Services (AWS). Skilled in problem-solving with a proven track record of delivering scalable, along with a commitment to continuous learning and professional growth. A team player with a growing ability to collaborate effectively in cross-functional teams. Eager to learn and contribute to innovative projects while building technical skills and staying up-to-date with the latest technology trends.