What is JWT? JSON Web Tokens Explained for Developers

5 min read
Intermediate JWT Authentication Security API

If you have worked with any modern web application or REST API, you have probably encountered JSON Web Tokens. JWTs are the most common way to handle authentication in single-page apps, mobile apps, and microservices. They are compact, stateless, and widely supported.

But they are also widely misunderstood. Developers use JWTs without understanding what is inside them, how to validate them properly, or what the security implications are. This guide explains everything you need to know.

What is a JWT?

A JSON Web Token is a compact, URL-safe string that represents a set of claims. It is used to securely transmit information between two parties — typically a client (browser or app) and a server.

A JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlNhbSIsImlhdCI6MTcxMTAwMDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

It is three Base64-encoded JSON strings separated by dots:

HEADER.PAYLOAD.SIGNATURE

That is it. No encryption, no binary format — just three JSON objects encoded in Base64.

The Three Parts

Header

The header specifies the token type and the signing algorithm:

{
  "alg": "HS256",
  "typ": "JWT"
}

Common algorithms:

  • HS256: HMAC with SHA-256 (symmetric — same secret key for signing and verifying)
  • RS256: RSA with SHA-256 (asymmetric — private key signs, public key verifies)
  • ES256: ECDSA with SHA-256 (asymmetric, smaller keys than RSA)

Payload

The payload contains the claims — the actual data you want to transmit:

{
  "sub": "1234567890",
  "name": "Sam",
  "email": "[email protected]",
  "role": "admin",
  "iat": 1711000000,
  "exp": 1711086400
}

Important: The payload is Base64-encoded, NOT encrypted. Anyone can decode it and read the contents. Never put secrets, passwords, or sensitive data in a JWT payload.

Standard Claims

Claim Full Name Purpose
iss Issuer Who issued the token
sub Subject Who the token is about (usually user ID)
aud Audience Who the token is intended for
exp Expiration When the token expires (Unix timestamp)
nbf Not Before Token is not valid before this time
iat Issued At When the token was created
jti JWT ID Unique identifier for the token

You can add any custom claims you want (like role, email, permissions).

Signature

The signature verifies that the token has not been tampered with:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

The server creates the signature using a secret key (or private key for RS256). When the token comes back, the server recalculates the signature and compares. If someone modified the payload, the signatures will not match, and the token is rejected.

The Authentication Flow

Here is how JWT authentication typically works:

Step 1: Login The user sends their credentials (username/password) to the server.

POST /api/login
{ "email": "[email protected]", "password": "..." }

Step 2: Server Creates Token The server verifies the credentials, creates a JWT with the user's info, signs it, and sends it back.

{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Step 3: Client Stores Token The client stores the token (typically in memory or localStorage) and includes it in every subsequent request.

Step 4: Authenticated Requests Every API request includes the token in the Authorization header:

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

Step 5: Server Validates The server checks the signature, expiration, and claims. If valid, it processes the request. No database lookup needed — the token itself contains all the information.

Access Tokens vs Refresh Tokens

Short-lived access tokens are more secure (if stolen, they expire quickly), but users do not want to log in every 15 minutes. The solution: refresh tokens.

Token Lifetime Purpose Storage
Access Token 15 min–1 hour Authenticate API requests Memory
Refresh Token 7–30 days Get new access tokens HttpOnly cookie

The flow:

  1. User logs in → gets both tokens
  2. Access token is used for API calls
  3. When access token expires, the client sends the refresh token to get a new access token
  4. If the refresh token expires, the user must log in again

JWT vs Sessions

JWT Sessions
State Stateless (token contains everything) Stateful (server stores session data)
Storage Client-side Server-side (memory, Redis, database)
Scalability Excellent (any server can validate) Requires shared session store
Revocation Hard (cannot invalidate a token easily) Easy (delete the session)
Size Larger (contains claims data) Small (just a session ID)
Best for APIs, microservices, mobile apps Traditional server-rendered apps

Security Best Practices

Do

  • Set short expiration times (15 minutes for access tokens)
  • Use HTTPS only — tokens are not encrypted, just signed
  • Validate everything: signature, expiration, issuer, audience
  • Use RS256 for public-facing APIs (asymmetric — you can share the public key for verification without exposing the signing key)
  • Store access tokens in memory, not localStorage (XSS can steal localStorage)
  • Store refresh tokens in HttpOnly cookies (inaccessible to JavaScript)

Do Not

  • Never put secrets in the payload — it is Base64, not encrypted
  • Never skip signature verification — this is how token forgery happens
  • Never use alg: none — some libraries accept unsigned tokens if you do not explicitly reject them
  • Never use JWTs as session storage — if you need to store lots of data, use sessions
  • Never trust the client — always validate on the server

The alg: none Attack

A classic JWT vulnerability: the attacker changes the header to "alg": "none" and removes the signature. If the server library does not explicitly require a specific algorithm, it might accept the unsigned token as valid. Always specify the expected algorithm when verifying:

# Python (PyJWT)
jwt.decode(token, secret, algorithms=["HS256"])  # Good — explicit

jwt.decode(token, secret, algorithms=["none", "HS256"])  # BAD

Decode a JWT

Use our free JWT Decoder to paste any JWT and instantly see the decoded header, payload with human-readable claim labels, expiration status, and signature. All decoding happens in your browser — your token is never sent to any server.

See Also