Skip to content
Lesson 9 of 14

Step 1 of 5 · Reading · ~3 min

Read

Production Concerns

Putting It All Together

Time to assemble an object that both mints and checks tokens. You have every piece: base64url, the header and payload encoded as the exact bytes you sign, HS256 over header.payload compared in constant time, claim validation with leeway, and kid so two keys can be right at once.

The order is the security

RFC 7519 §7.2 lays out validation as a numbered procedure, and the numbering is not decoration. Collapsed to what a JWS verifier actually does:

Rendering diagram…

Two steps carry the weight. "Is this algorithm one I accept?" happens before verification, from your configuration and not the token's opinion — RFC 7519 closes §7.2 saying even a cleanly-validating token should be rejected if its algorithm is unacceptable. And the payload is not read until the signature passes: every box above it handles untrusted bytes.

Minting a token

An issuer does three things: fill in the time claims, encode, sign.

python

Identical information, completely different tokens — the signature covers bytes, not meaning. Whitespace is legal (§7.1 requires no canonicalization), so nothing rejects the second; it just will not match a signature over the first. Serialize compactly, and never re-serialize bytes already on the wire.

A production checklist

  • Pin accepted algorithms in configuration; bind each key to the family it may be used with, and use header.alg only to check against that list.
  • Compare signatures in constant time. 32+ CSPRNG bytes for HMAC, 2048-bit RSA.
  • Short exp (minutes), leeway in seconds; validate aud and iss too.
  • Resolve keys by kid; refresh the store when a kid is unknown.
  • Never log a whole token — it is a bearer credential — and keep anything sensitive out of the payload.

Three more topics harden what you just assembled: rejecting alg:none, JWKS registries with kid lookup, and refresh-token rotation.

What sits outside JWT

  • JWE (RFC 7516) encrypts the payload — five segments. Sign first, then encrypt (§11.2).
  • Nested JWTs — a JWT as another's payload; the outer header sets cty to "JWT" (§5.2).
  • EdDSA / Ed25519 — deterministic, small keys, no RSA padding history.
  • PASETO and Biscuit — replacements that remove algorithm negotiation, so the first two attacks here cannot be expressed.
  • Opaque tokens plus a session store — still right for one application wanting instant revocation. JWT buys stateless cross-service verification; if you are not spending that, you are paying for nothing.

Your exercise

You will implement issue and verify for a complete HS256 service.

ISSUE is where submissions die: the grader compares the exact token string. Two rules the tests pin down:

  • Compact separators. For {"sub":"alice","role":"admin"} at NOW 1000 the token must end ...ImV4cCI6NDYwMH0.6e6X5cu8hqW5w4u51JvvX6JUbNZ4dxSsSzJ0mUtQc-0. Plain json.dumps(claims) spaces every : and , and gives ...ImV4cCI6IDQ2MDB9.1MrFR1qTNsvH8U9IjPP-pGaIP1SKkWGkVxBMK-bfi-4 — different payload, different signature. Use separators=(",", ":").
  • Append iat, then exp, keeping the caller's key order. A hidden case issues {"sub":"eve","exp":9999} and expects {"sub":"eve","exp":9999,"iat":1000}: exp was supplied so it keeps its place and only iat is appended. Sorting keys breaks the signature.

VERIFY returns BAD_SIG before it considers EXPIRED — the signature decides whether the claims are worth reading.

Up nextDefense: Reject alg:noneSecurity & Operations

Discussion

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

Sign in to post a comment or reply.

Loading…