Skip to main content

Command Palette

Search for a command to run...

JWT Authentication in Node.js Explained Simply

Updated
•6 min read•View as Markdown
JWT Authentication in Node.js Explained Simply
M

Full-stack developer with a good foundation in frontend, now specializing in backend development. Passionate about building efficient, scalable systems and continuously sharpening my problem-solving skills. Always learning, always evolving.

A developer ships a simple API with login support. Everything works fine until they hit a basic question: how does the server remember who the user is after login? HTTP requests are stateless. Each request is independent. There is no built-in memory of previous interactions.

That is where authentication systems like JWT come in.


What Authentication Actually Means

At a practical level, authentication answers one question:

"Who is making this request?"

When a user logs in with email and password, the server verifies those credentials. But verification alone is not enough. The system needs a way to recognize that same user in future requests without asking for credentials every time.

In real applications:

  • A dashboard should only show your data

  • An API should reject requests from unknown users

  • Admin routes should not be accessible to normal users

So authentication becomes a continuous process, not a one-time check.

Without it, every request is anonymous.


What JWT Is and Why It Exists

A JSON Web Token (JWT) is a compact string that represents a user's identity and some related data.

Formally, it is an open standard defined in RFC 7519 for securely transmitting information between parties (JSON Web Tokens - jwt.io).

But that definition is abstract. Here is what matters in practice:

  • A JWT is self-contained

  • It carries user identity inside the token

  • It is signed, so it cannot be tampered with unnoticed

This enables stateless authentication.

Why stateless matters

Traditional session-based systems work like this:

  • Server stores session data in memory or database

  • Client stores only a session ID

  • Every request requires a database lookup

JWT flips this model:

  • Server does not store session state

  • Client stores the token

  • Server validates the token on each request

This removes the need for a session store and makes horizontal scaling simpler.

That is why JWT is heavily used in APIs, microservices, and distributed systems (Stytch).


Structure of a JWT

A JWT is just a string with three parts separated by dots:

header.payload.signature

Each part is Base64URL encoded.

1. Header

Contains metadata about the token:

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg defines the signing algorithm

  • typ defines the token type

2. Payload

Contains claims, which are pieces of information:

{
  "sub": "user123",
  "role": "admin",
  "exp": 1712345678
}

Typical claims include:

  • sub: user identifier

  • exp: expiration time

  • custom fields like roles or permissions

Important detail:

The payload is not encrypted by default. It is only encoded. Anyone can decode it.

3. Signature

This is what makes JWT trustworthy.

The server signs:

encoded(header) + "." + encoded(payload)

Using a secret or private key.

If someone modifies the payload:

  • Signature becomes invalid

  • Server rejects the token

So the signature ensures integrity, not secrecy (Medium).


Login Flow Using JWT

Let’s walk through a real system behavior.

Step-by-step flow

  1. User sends login request:
POST /login
{
  "email": "user@example.com",
  "password": "password"
}
  1. Server verifies credentials
  • Checks database

  • Compares hashed passwords

  1. Server generates JWT

Payload might include:

{
  "userId": "123",
  "role": "user"
}
  1. Server sends token back
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
  1. Client stores the token

Common options:

  • HTTP-only cookies (safer)

  • Memory storage

  • Avoid localStorage for sensitive apps

Diagram: JWT Login Authentication Flow

Visualize this pipeline:

  • Client → sends credentials → Server

  • Server → validates → generates JWT

  • Server → returns JWT → Client

  • Client → stores token

  • Client → sends token with future requests


Sending Token with Requests

After login, every request must carry identity.

The most common approach:

Authorization: Bearer <token>

Example:

GET /api/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Why this works:

  • Token contains user identity

  • Server does not need to look up session data

  • Each request is independently verifiable

This is fundamentally different from cookies + sessions.


Protecting Routes Using JWT

In Node.js with Express, this is typically done using middleware.

Conceptual flow

  1. Request hits protected route

  2. Middleware extracts token

  3. Middleware verifies token signature

  4. Middleware attaches user data to request

  5. Route handler executes

Example (simplified)

const jwt = require("jsonwebtoken");

function authMiddleware(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader) {
    return res.status(401).json({ message: "Missing token" });
  }

  const token = authHeader.split(" ")[1];

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(403).json({ message: "Invalid token" });
  }
}

What is happening under the hood

  • jwt.verify checks:

    • Signature validity

    • Expiration time

    • Token integrity

If validation fails:

  • Request is rejected

This aligns with best practices where every request must validate the token fully (Curity).


Token Validation Lifecycle

Diagram: Token Validation Lifecycle

Visualize this:

  • Request → includes JWT

  • Server → extracts token

  • Server → verifies signature

  • Server → checks expiration and claims

  • If valid → allow access

  • If invalid → reject request


Why JWT Works (and Where It Fails)

Why it works well

  • No session storage required

  • Easy horizontal scaling

  • Works across services

  • Self-contained identity

Trade-offs you should care about

  1. Revocation is hard

Once issued, tokens remain valid until expiration. You cannot easily "log out everywhere" without extra infrastructure.

  1. Payload visibility

Anyone can decode the token. Never store sensitive data inside it.

  1. Security depends on implementation

JWT is just a format. It is not secure by default. Misuse breaks it (Curity).


Final Mental Model

Think of JWT like a signed identity card:

  • Server issues it after verifying you

  • You carry it with every request

  • Server trusts it because it can verify the signature

  • No central memory is required

That design is what makes JWT powerful in distributed systems.

But it also means:

  • You are pushing responsibility to the client

  • You must validate everything on every request

That is the real trade-off behind JWT authentication.