DEV Community

Charity Atieno
Charity Atieno

Posted on

What Are JWT Tokens and How Do They Work? A Beginner-Friendly Guide

Posted as part of my build-in-public series on Kaya, a verified house-hunting platform I am building for the Kenyan market.

JSON web tokens Authentication

If you have ever logged into a website and stayed logged in as you moved from page to page, something behind the scenes was keeping track of who you are. One of the most common ways modern backends handle this is with JWT tokens. When I started building the authentication system for Kaya, JWTs were one of the first things I had to truly understand, not just use from a tutorial, but actually get. This article is my attempt to explain them the way I wish someone had explained them to me.
The Problem JWTs Solve
Before JWTs, the most common approach to authentication was sessions. Think of it like a coat check at a restaurant. You hand over your coat, the attendant gives you a ticket with a number on it, and every time you want your coat back you show the ticket and they look up your coat in the back room. Simple enough when there are ten people. But what happens when there are ten thousand? The back room gets crowded, the lookups get slower, and the whole system starts to strain.

That is exactly what happens with session-based authentication at scale. You log in, the server creates a session record in a database, and sends you a session ID as a cookie. Every single request you make sends that session ID back, and the server has to look it up in the database every single time. As your application grows, this puts real pressure on your infrastructure.
JWTs offer a completely different approach, one where the server does not need to store anything at all.

So What Is a JWT?
JWT stands for JSON Web Token. Instead of the coat check giving you a ticket and keeping your coat in a back room, imagine they stamped all your details directly onto a card, sealed it with wax, and gave it back to you. Now whenever you show up, they just read the card and check the seal. No back room. No lookups. If the seal is intact, you are good.

That is a JWT. It is a compact, self-contained string that carries the user's information and a cryptographic signature to prove it has not been tampered with. A JWT looks something like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYWJjLTEyMyIsImV4cCI6MTcwMDAwMDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Enter fullscreen mode Exit fullscreen mode

Looks like chaos, right? It is actually three parts separated by dots: the header, the payload, and the signature. Let us break each one down.
Part One: The Header
The header is the least exciting part. It simply tells the server what type of token this is and which algorithm was used to sign it.

{
  "alg": "HS256",
  "typ": "JWT"
}
Enter fullscreen mode Exit fullscreen mode

HS256 stands for HMAC with SHA-256, a secure and widely used signing algorithm. This object is then Base64URL encoded to produce the first chunk of the token. Nothing secret here, just metadata.

Part Two: The Payload
This is where the actual information lives. The payload contains what are called claims, which are just key-value pairs describing the user or the token itself.

{
  "user_id": "abc-123",
  "phone": "254712345678",
  "exp": 1700000000
}
Enter fullscreen mode Exit fullscreen mode

Some common claims you will encounter are sub, which is the subject and usually holds the user's ID, exp which is the expiry timestamp telling the server when this token stops being valid, and iat which is the issued-at time recording when the token was created.

Here is the part that trips a lot of beginners up: the payload is encoded, not encrypted. That means anyone who gets hold of a JWT can decode it and read everything inside. Go ahead and paste any JWT into jwt.io and you will see exactly what I mean. So the golden rule is this: never put sensitive information like passwords or secret keys inside a JWT payload.

Part Three: The Signature
This is where the trust lives. The signature is created by taking the encoded header and payload, combining them, and running them through a hashing algorithm using a secret key that only your server knows.

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  your-secret-key
)
Enter fullscreen mode Exit fullscreen mode

Think of the signature as the wax seal on that card from our earlier analogy. When someone presents a token, your server re-runs this calculation using its secret key. If the signature matches, the token is genuine. If someone has tampered with the payload, even by changing one single character, the signature will not match and the server will reject the token immediately.

This is the elegant part of JWTs: the server can verify who you are without asking anyone or looking anything up. The token proves itself.

How the Full Flow Works

Let us walk through what actually happens when a user logs in to an app that uses JWTs.

User sends credentials to the server
         |
Server verifies the credentials
         |
Server creates a JWT with the user's ID and expiry,
signs it with the secret key, and sends it back
         |
User stores the token (in memory or localStorage)
         |
For every future request, the user sends:
Authorization: Bearer <token>
         |
Server reads the token, verifies the signature,
checks the expiry, knows who you are.
No database. No session lookup. Done.
Enter fullscreen mode Exit fullscreen mode

You will notice the word Bearer before the token. It is just a convention that tells the server what kind of credential is being sent. It means "the person presenting this token should be granted access." Nothing more to it than that.

Why Tokens Cannot Live Forever
Every JWT should have an expiry time. In the Kaya backend I set it to 24 hours. Here is why this matters: unlike sessions, a JWT cannot be invalidated by the server once it has been issued. If someone gets hold of your token, they can use it freely until it expires. Setting a short expiry limits the damage window if a token is ever compromised.

This leads to one of the core trade-offs of JWTs. They are fast, stateless, and scale beautifully because the server stores nothing. But they are also difficult to revoke. If you need to log a user out immediately, for example if their account is suspended, you have to build additional mechanisms on top of the basic JWT approach to handle that. For most applications though, a sensible expiry time is enough.

How This Fits Into Kaya
In the Kaya backend, JWTs sit at the heart of the authentication system. When a user successfully verifies their identity, the server creates a JWT containing their unique ID, signs it with a secret key stored securely in the environment, and returns it to the client. From that point on, every request to a protected route must include that token. A middleware function intercepts each request, validates the token, and either allows it through or returns a 401 Unauthorized response.
The result is a backend that never stores login state. Every request is self-contained and verified on the spot. For a platform like Kaya where access to certain listing details is gated behind verified identity and payment, having a reliable and stateless authentication layer is not optional; it is foundational.

Key Takeaways

  • A JWT is made of three parts: the header, the payload, and the signature
  • The signature is what makes it trustworthy and proves nothing has been tampered with
  • The payload is readable by anyone, so sensitive data should never go inside it
  • Tokens should always have an expiry time to limit damage if one is ever compromised
  • The server verifies everything using a secret key with no database lookup required
  • JWTs are stateless, fast, and scale well but they cannot be invalidated once issued without additional mechanisms

Top comments (0)