Back to portfolioMLH Fellowship

Building JWT from Scratch

MLH Software Engineering Fellowship, code sample submission

This is my MLH fellowship code sample: a JSON Web Token library written from scratch in pure Python. No AI, no external libraries, dependencies = [] on purpose. Even the base64url codec is hand-rolled. I screen-recorded every build session, so below, each step links to the exact video and timestamp where it happened.

Day 3 — Day 5

Setup, and planning the codec by hand

  1. Day 3

    Project setup. I set dependencies = [] in pyproject.toml on purpose, to declare that this project runs on nothing but the Python standard library: hashlib, hmac, json, and time. Then I set up a Makefile to run the test suites quickly, because the whole plan was test driven development.

  2. Day 4

    Wrote the base64url test suite before implementing anything. Known-answer tests from the RFC 4648 vectors, round-trip tests that push bytes through encode then decode and expect the original back, and rejection tests for '=' padding since the url-safe form strips it. Finished with test_reject_impossible_length: a base64url string can never have a length of 4n + 1. To prove the tests themselves were right, I temporarily wired Python's own base64 urlsafe functions in place of my stubs, watched everything pass, then took the library back out. The encode test suite session before this one missed the recording.

  3. Day 5

    Planned my own encoder to replace the borrowed library functions. Before starting this project I had spent 20+ hours working through JWT internals by hand in GoodNotes on my iPad, and this session is where I review those notes on camera: how the payload becomes binary, how 8-bit groups get regrouped into 6-bit ones, and how padding is stripped to get the url-safe form. Then I turned the notes into small problems inside b64url_encode.

Day 9 — Day 16

Decode: stuck, then finished

  1. Day 9

    An entire session spent trying to get decode working, ending with the test suite still failing. I kept it in the playlist anyway. Getting stuck is part of the record.

  2. Day 16

    The day the decoder got finished, and the most important session for me. I broke the problem down on paper first, working out how decode inverts encode and what I was getting wrong. Deleted the old decode function and restarted from a stub. To verify the character-to-index mapping I opened the Python debugger inside nvim, set breakpoints, and stepped through the alphabet list. Did the same for the 6-bit conversion. By the end of the session every test passed.

Day 17 — Day 22

Errors, and the encode API

  1. Day 17

    Started the exception hierarchy: one base error, then pre-auth errors for tokens that cannot be trusted at all, versus validation errors for authentic tokens that fail a claim check like expiry or audience.

  2. Day 19

    Designed the encode API in Excalidraw (the design part itself missed the recording), then spent the session writing TDD unit tests against stub functions, working from the official unittest documentation.

  3. Day 20

    Implemented the encode API. Kept it as functional as I reasonably could, one role per function. Broke the API into parts, made each part a stub, so the whole problem reduced to filling in stubs one at a time.

  4. Day 21

    One test class per stub function, several test methods in each. Write the suite, watch it fail against the stub, then implement until green.

  5. Day 22

    Finished the encode API, testing and implementation both.

Day 22 — Day 26

The decode API, and the driver

  1. Day 22, Session 2

    Designed the entire decode API in Excalidraw before writing a single line of code.

  2. Day 23

    Turned the decode design into stub functions, breaking the API down into small single-purpose units.

  3. Day 24

    Test classes for the decode stubs, same pattern as encode: fail first, then implement.

  4. Day 25

    Completed the decode API and verified it against jwt.io's official example, using their payload and secret.

  5. Day 26

    Wrapped the project with a small main driver: an auth service issues a token and verifies it, then an attacker rewrites the payload and gets rejected with InvalidSignature.

Some moments missed the recording, like the encode test suite and parts of the Excalidraw design sessions. What did get recorded is unedited, including the sessions where nothing worked.