Skip to content
Lesson 1 of 14

Step 1 of 5 · Reading · ~4 min

Read

JWT Anatomy

JWT Structure: header.payload.signature

A JWT is one long string with two dots in it. Everything a server needs to answer "who is this, and what may they do?" rides in those three segments — no session table, no lookup, no call back to the login service.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiJ9.XaybqYCpG9G33sm03ZZRA3x1axtm8cRBBlsVnp8L6FI
└─────────── header ───────────────┘ └── payload ──┘ └──────────── signature ────────────────┘

Who computes what, and when?

Rendering diagram…

Notice what the API server never does: it never asks the auth server about this user. It recomputes the signature locally. Verify locally, trust nothing else — that is the entire reason JWTs exist.

What is actually inside the three parts?

The first two segments are ordinary JSON, base64url-encoded. The third is the raw output of a signing algorithm, encoded the same way.

python

Why base64url instead of plain base64?

Standard base64 emits +, / and =. All three are hostile where tokens live: in a URL + means a space, / is a path separator, = is a key/value delimiter. RFC 4648 §5 defines a URL-safe alphabet, and JWT uses it throughout.

standard base64base64url
value 62+-
value 63/_
padding= up to a multiple of 4stripped entirely

The same 32 signature bytes written both ways:

base64     vLynhsWfwoXvF1pzEl98zLzkpJ+m/hDYMVqvmHH4Bvs=
base64url  vLynhsWfwoXvF1pzEl98zLzkpJ-m_hDYMVqvmHH4Bvs

All three differences at once: + became -, / became _, = is gone.

How much padding was dropped?

inputbase64base64urldropped
fZg==Zg2
foZm8=Zm81
fooZm9vZm9v0
foobZm9vYg==Zm9vYg2
foobaZm9vYmE=Zm9vYmE1

To decode you put the padding back, and the string's length tells you how much:

pad=(4(Lmod4))mod4\text{pad} = (4 - (L \bmod 4)) \bmod 4

Zm9vYg has L=6L = 6 and 6mod4=26 \bmod 4 = 2, so two = go back on. A remainder of 1 is impossible — no whole number of bytes encodes to a length one more than a multiple of four. Compute a remainder of 1 and the segment is corrupt, not short.

The idiom and the trap

python
python

Signed is not encrypted

A signature proves the payload was not altered and came from someone holding the key. It hides nothing — as the code above shows, anyone with the token reads every claim in it. Never put a password, a card number, or anything you would not print in an access log inside a JWT. If the payload must be secret you need JWE, a different structure with five segments.

Your exercise

You will write b64u_encode and b64u_decode.

The hidden case ENCODE foob expects exactly Zm9vYg. Return base64.urlsafe_b64encode(...) without .rstrip("=") and you produce Zm9vYg== — two characters too long, and that is the entire test. Going the other way, DECODE Zm9vYg raises a padding error unless you re-add those two = first. The decode cases are deliberately chosen so one needs 0 padding characters, one needs 1, and one needs 2: a solution that hardcodes any single amount passes exactly one of them.

Up nextHeader & PayloadJWT Anatomy

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…