Skip to main content

Command Palette

Search for a command to run...

JWT Authentication in Node.js Explained Simply

Updated
12 min read
JWT Authentication in Node.js Explained Simply

1. What authentication means

2. What JWT is

Introduction

Every time you log into a website, a fundamental question arises: how does the server know who you are on the next request? Traditionally, servers maintained a session record — but this breaks down when your app runs across multiple servers or serves millions of users. JWT offers a smarter, stateless alternative.

What is JWT?

A JSON Web Token (JWT, pronounced "jot") is a compact, self-contained token that securely carries information between two parties. Unlike a hotel room key that works because the hotel tracks it, a JWT is more like a signed passport — the server can verify your identity simply by reading it, with no lookup required.

A JWT is a string of three base64url-encoded sections joined by dots:

eyJhbGciOiJIUzI1NiJ9  .  eyJzdWIiOiJ1c2VyXzEyMyJ9  .  SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
    Header                      Payload                          Signature

How it Works

The flow begins when a user logs in and ends — on every subsequent request — with the server verifying the token entirely on its own, without touching a database.

Stateless authentication is what makes this powerful. After issuing the token, the server stores nothing. Every subsequent request carries the token, and the server simply reads and verifies it — no session lookup, no database query. This scales effortlessly across multiple servers, because any server that knows the secret key can validate any token.

Each JWT is made of three parts. The header declares the algorithm used to sign it (typically HS256 or RS256). The payload carries claims — structured facts like the user's ID (sub), when the token was issued (iat), and when it expires (exp). The signature binds the other two parts together using a secret key; tamper with anything, and the signature no longer matches.

Here is how the server validates an incoming token:

No database query, no shared session store — just math.

Examples

After a successful login, the server returns a token the client stores in memory or localStorage. Every subsequent request includes it in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTcxMzc0MDgwMH0.abc123

Decode the payload (the middle section) and you get plain JSON:

{
  "sub": "user_123",
  "iat": 1713654400,
  "exp": 1713740800,
  "roles": ["admin"]
}

The server recomputes the signature using its secret key and checks that it matches the one in the token. If the payload has been tampered with even slightly, the signature will not match and the request is rejected. Expiry is enforced the same way — the server reads exp and compares it to the current time.

Conclusion

JWT solves a real problem cleanly. Authentication no longer requires every server to share a session store, and scaling becomes straightforward. The trade-off is that tokens cannot be revoked mid-flight — once issued, a JWT is valid until it expires, which is why short expiry times (15–60 minutes) paired with refresh tokens are the standard practice in production systems.


3.Structure of a JWT:

  • Header

  • Payload

  • Signature

Introduction

A JWT is not a single blob of data — it's three distinct, purpose-built pieces joined by dots. Each part answers a different question: what algorithm?, who is this user?, and can I trust this token? Understanding each part in isolation makes the whole system click.

Definition

A JWT (JSON Web Token) is three base64url-encoded JSON objects concatenated with dots:

<Header>.<Payload>.<Signature>

Each section can be decoded independently. None of them are encrypted by default — the signature is what provides trust, not secrecy. This is the most common misconception about JWTs.

Explanation

Each of the three parts has a distinct role:

The header is the metadata. It declares which algorithm was used to sign the token — typically HS256 (symmetric, shared secret) or RS256 (asymmetric, public/private key). This tells the server exactly how to verify the signature.

The payload carries the claims — structured facts the server needs. Standard claims include sub (the user's identity), iat (when the token was issued), and exp (when it expires). Custom claims like roles or permissions can be freely added. Because the payload is only encoded, not encrypted, anyone who intercepts the token can read it — never store passwords or sensitive data here.

The signature is the integrity seal. It is computed by running the base64url-encoded header and payload together through the declared algorithm using a secret key. If anything in the header or payload changes — even a single character — the signature will no longer match. This is how tampering is detected.

Here is how all three parts fit together inside a single token:

Examples

A raw JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiJ1c2VyXzEyMyIsImlhdCI6MTcxMzY1NDQwMCwiZXhwIjoxNzEzNzQwODAwLCJyb2xlcyI6WyJhZG1pbiJdfQ
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Paste the middle section into any base64url decoder and the payload becomes readable JSON:

{
  "sub": "user_123",
  "iat": 1713654400,
  "exp": 1713740800,
  "roles": ["admin"]
}

This illustrates the critical point: the payload is visible to anyone who holds the token. The signature ensures it cannot be changed without detection, but it does nothing to hide the contents. For truly sensitive data, a separate encryption layer (JWE) is needed.

Conclusion

The three-part structure of a JWT gives each concern its own lane: the header handles algorithm negotiation, the payload carries identity and authorization data, and the signature enforces integrity. The server never needs to look anything up — it reads the header, recomputes the signature, checks the expiry, and responds. That statelessness is both JWT's greatest strength and its main caveat: once issued, a token cannot be invalidated until it naturally expires, which is why keeping expiry times short (15–60 minutes) remains the single most important security practice.


4. Login flow using JWT

Introduction

Every application needs to know who is making a request before it decides what to allow. Without authentication, any visitor could read private data, act as another user, or trigger operations they have no right to perform. Authentication is the gatekeeper — it verifies identity so that the system can make informed decisions about access.


Definition

JWT stands for JSON Web Token, defined in RFC 7519. It is a compact, self-contained string that encodes a set of claims — statements about the user, such as their ID, role, and when the token expires — and protects them with a cryptographic signature. Anyone with the right secret key can verify the token is genuine and unmodified, without asking an external system.

Explanation

Traditional session-based authentication keeps login state on the server: a record in a database or memory store, referenced by a session ID cookie. JWT turns this around. The token itself carries the user's identity, and the server holds no state at all. This is called stateless authentication — each request is self-sufficient.

A JWT is made of three base64url-encoded parts joined by dots:

  • Header — declares the signing algorithm (e.g. HS256) and token type

  • Payload — contains claims: user ID, role, issued-at time, expiry

  • Signature — a cryptographic hash of the header and payload, signed with a secret key

The server validates a token by re-computing that signature and comparing it. If it matches, the payload is trustworthy. If the payload was altered even by a single character, the signature check fails.

Here is the full login flow:

Once the client has a JWT, every subsequent request carries it in the Authorization header as Bearer <token>. The server then runs a fast, stateless check — no session table required. Here is what that validation looks like inside the server:

Examples

A real JWT token in transit looks like this in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
                      .eyJ1c2VySWQiOiI0MiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMjAwMDAwMH0
                      .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

When the server decodes that middle part (the payload), it reads something like:

{
  "userId": "42",
  "role":   "admin",
  "exp":    1712000000
}

The server never looked up a session table. It simply re-signed the first two parts with its secret key and confirmed the signature matched. The payload is then trusted and the request is authorised.

Conclusion

JWT-based login moves authentication state off the server and into a self-verifying token. The benefits are real: no shared session store, straightforward scaling across multiple services, and a clean contract between client and server. The trade-offs are also real: tokens cannot be invalidated before expiry without extra infrastructure (like a deny-list), and the secret key must be guarded carefully. Used with short expiry windows and refresh token rotation, JWT is a robust foundation for modern authentication.


5. Sending token with requests

Introduction

Every time you log into an app and keep browsing without re-entering your password, something invisible is doing the heavy lifting — that's a token. Modern web APIs are stateless, meaning the server doesn't remember who you are between requests. Tokens solve this by letting the client carry a small, verifiable proof of identity with every request.


Definition

A token is a digitally signed string that encodes identity and permissions. The most common format is a JWT (JSON Web Token) — a compact, URL-safe string split into three parts: a header, a payload, and a signature. The server issues it once; the client sends it back on every subsequent request.


Explanation

Here's how stateless token-based authentication works in practice:

The key insight: the server never stores session state. It only needs its secret key to verify a token's signature — no database lookup required on every request.


Example

Here's how you'd send a JWT with a real API call in JavaScript:

// Step 1 — Login and receive the token
const loginRes = await fetch('/api/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ username: 'alice', password: 'secret' }),
});
const { token } = await loginRes.json();

// Step 2 — Attach the token to every protected request
const dataRes = await fetch('/api/dashboard', {
  headers: {
    'Authorization': `Bearer ${token}`,
  },
});
const data = await dataRes.json();

The token travels in the Authorization header with the Bearer prefix — a widely adopted convention that tells the server exactly where to look.

Conclusion

Token-based authentication solves the fundamental problem of stateless HTTP: how does the server know who is making a request? By issuing a signed JWT at login and requiring clients to echo it back on every call, the server stays lightweight and scalable — no session tables, no sticky sessions. Once you understand the flow (login → receive token → attach to requests → server validates), you'll recognize this pattern in virtually every modern API you work with.


6. Protecting routes using tokens

Introduction

Not every route in a web application should be accessible to everyone. A /dashboard, /profile, or /admin endpoint must only respond to users who are actually logged in. Without route protection, anyone who knows the URL can access sensitive data — no questions asked. Tokens are the mechanism that enforces the boundary between public and private routes.


Definition

Route protection means placing a guard — typically server-side middleware — in front of certain endpoints. This middleware inspects every incoming request for a valid token before deciding whether to let it through. If the token is present, well-formed, and unexpired, the request proceeds. If anything fails, the server responds with a 401 Unauthorized and the protected resource is never touched.


Explanation

The protection logic runs in two phases. First, the user logs in and receives a JWT. Second, every subsequent request to a protected route passes through a validation layer before any handler runs.

Here's how the login phase produces the token that guards subsequent requests:

Once a token is in the client's hands, every call to a protected route triggers the middleware's validation lifecycle — a series of checks the token must pass before the route handler even runs:

Every check is a gate. If the token is missing, malformed, tampered with, or expired — the request is rejected before it gets anywhere near the route logic.

Example

Here's how you'd implement a token-protection middleware in Node.js using Express and the jsonwebtoken library:

const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;

// Middleware: protect any route it's attached to
function protect(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // strip "Bearer "

  if (!token) return res.status(401).json({ error: 'No token provided' });

  jwt.verify(token, SECRET, (err, decoded) => {
    if (err) return res.status(401).json({ error: 'Invalid or expired token' });
    req.user = decoded; // attach user claims to the request
    next();             // pass control to the actual route handler
  });
}

// Public route — no guard
app.get('/public', (req, res) => res.json({ message: 'Anyone can see this' }));

// Protected route — token required
app.get('/dashboard', protect, (req, res) => {
  res.json({ message: `Welcome, ${req.user.name}` });
});

The protect function acts as a reusable gate. Attach it to any route that needs guarding, and unauthenticated requests will never reach the handler.

Conclusion

Protecting routes is the real-world payoff of token-based authentication. The server doesn't need sessions or a lookup table — it simply trusts the signature. By placing a validation middleware in front of sensitive endpoints, you draw a clean line between what's public and what requires identity. The token carries that identity on every trip, and the middleware decides whether it's allowed in.