JWT
token decoding
JSON Web Token
authentication
web security

How to decode JWT Token?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A JWT is just a compact string with three dot-separated parts: header, payload, and signature. Decoding it is useful for inspection and debugging, but decoding alone does not prove the token is trustworthy.

That distinction is the most important thing to understand. Anyone who has the token can base64url-decode the first two sections. Only signature verification tells you whether the token was issued by a trusted signer and has not been modified.

What It Means to Decode a JWT

A JWT usually looks like this:

text
header.payload.signature

The header and payload are base64url-encoded JSON. The signature is a cryptographic check value created from the first two parts plus a secret or private key.

So there are really two separate operations:

  • decode the token to read claims
  • verify the token before trusting those claims

If you only need to inspect the contents during development, decoding is enough. If the token influences authentication or authorization, verification is mandatory.

Decoding Without Verification

Here is a Python example using PyJWT to read the payload without verifying the signature:

python
1import jwt
2
3token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiTWFyayIsImFkbWluIjpmYWxzZX0.fake-signature"
4
5payload = jwt.decode(
6    token,
7    options={"verify_signature": False},
8    algorithms=["HS256"],
9)
10
11print(payload)

This is useful for debugging, but the resulting claims must be treated as untrusted input.

You can also manually decode the payload to understand what is happening:

python
1import base64
2import json
3
4token = "aaa.bbb.ccc"
5header_b64, payload_b64, signature_b64 = token.split(".")
6
7def decode_base64url(value):
8    padding = "=" * (-len(value) % 4)
9    return base64.urlsafe_b64decode(value + padding)
10
11payload = json.loads(decode_base64url(payload_b64))
12print(payload)

That demonstrates the structure, but it does not validate the signature.

Verifying the JWT Correctly

In real applications, use a library that verifies the signature and claims in one step. With a shared-secret algorithm such as HS256, the server verifies using the same secret that was used to sign the token.

python
1import jwt
2
3token = "<signed token here>"
4secret = "super-secret-key"
5
6payload = jwt.decode(
7    token,
8    secret,
9    algorithms=["HS256"],
10)
11
12print(payload["sub"])

If the signature is wrong, the algorithm does not match, or the token is expired, the library raises an exception.

In Node.js with jsonwebtoken, the flow is similar:

javascript
1const jwt = require("jsonwebtoken");
2
3const token = process.env.ACCESS_TOKEN;
4const secret = process.env.JWT_SECRET;
5
6try {
7  const payload = jwt.verify(token, secret, { algorithms: ["HS256"] });
8  console.log(payload.sub);
9} catch (error) {
10  console.error("Invalid token:", error.message);
11}

Use verify, not just decode, when security decisions depend on the result.

Reading Common Claims

Once the token is verified, you can inspect standard claims such as:

  • 'sub for subject or user identifier'
  • 'exp for expiration time'
  • 'iat for issued-at time'
  • 'iss for issuer'
  • 'aud for audience'

Libraries can validate some of these automatically if you provide the expected values. For example, checking aud and iss helps prevent accepting tokens meant for a different service.

python
1payload = jwt.decode(
2    token,
3    secret,
4    algorithms=["HS256"],
5    audience="api://orders-service",
6    issuer="https://auth.example.com",
7)

That is much safer than verifying only the signature and then accepting every claim at face value.

Why Base64 Decoding Is Not Enough

Because the payload is only encoded, not encrypted, anyone with the token can read its claims. A malicious user can also edit the header and payload locally and produce a new string. Without signature verification, your application cannot distinguish a valid token from a forged one.

This is the core security rule: decode for visibility, verify for trust.

Common Pitfalls

The biggest mistake is calling a decode helper and then treating the payload as authenticated identity data. That creates an authorization bug immediately.

Another issue is accepting whatever algorithm the token header says. Good libraries let you specify the allowed algorithms explicitly. Do that, rather than trusting the token to declare its own rules.

Expiration handling also trips people up. A successfully decoded token may still be expired. Use verification methods that enforce exp, and handle clock skew only when you have a clear reason.

Finally, do not assume JWT contents are secret. Unless a separate encryption standard is used, JWT payloads are readable by anyone who has the token. Avoid putting sensitive data in them.

Summary

  • A JWT has header, payload, and signature sections.
  • Decoding reads the claims, but it does not prove the token is valid.
  • Use library verification methods such as jwt.decode with a key in Python or jwt.verify in Node.js.
  • Validate claims such as exp, iss, and aud when they matter.
  • Treat decoded but unverified payload data as untrusted input.

Course illustration
Course illustration

All Rights Reserved.