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:
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.
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.algonly to check against that list. - Compare signatures in constant time. 32+ CSPRNG bytes for HMAC, 2048-bit RSA.
- Short
exp(minutes), leeway in seconds; validateaudandisstoo. - Resolve keys by
kid; refresh the store when akidis 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
ctyto"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"}atNOW 1000the token must end...ImV4cCI6NDYwMH0.6e6X5cu8hqW5w4u51JvvX6JUbNZ4dxSsSzJ0mUtQc-0. Plainjson.dumps(claims)spaces every:and,and gives...ImV4cCI6IDQ2MDB9.1MrFR1qTNsvH8U9IjPP-pGaIP1SKkWGkVxBMK-bfi-4— different payload, different signature. Useseparators=(",", ":"). - Append
iat, thenexp, keeping the caller's key order. A hidden case issues{"sub":"eve","exp":9999}and expects{"sub":"eve","exp":9999,"iat":1000}:expwas supplied so it keeps its place and onlyiatis appended. Sorting keys breaks the signature.
VERIFY returns BAD_SIG before it considers EXPIRED — the signature decides
whether the claims are worth reading.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…