Lesson 10 · Phase 4 · Production 33-min capstone, coded live

The Whole Thing: Agent → FastAPI → JWT

The 33-minute capstone, coded live in Colab: the full agent behind a FastAPI /chat endpoint, JWT login with hashed passwords, guarded routes, forged-token and expiry tests — a production-shaped agentic API from an empty notebook.

What you'll learn

  • Serve an agent as an async REST API with FastAPI
  • Mint, verify and expire JWTs; hash passwords properly
  • Guard endpoints with OAuth2 bearer dependencies
  • Walk the full login → token → protected /chat pipeline

JWT playground

A JWT is three base64url chunks: header.payload.signature. The decoder below is the notebook's peek(), running live in your browser:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiJhbGljZSIsImV4cCI6MTc5OTk5OTk5OX0 . 3vJk4XbF0qPzR8wYtN2mHc5dLuKaGeSAoQx17EnUyfM

header payload signature

peek(header)

{
  "alg": "HS256",
  "typ": "JWT"
}

peek(payload)

{
  "sub": "alice",
  "exp": 1799999999
}

exp = Fri, 15 Jan 2027 07:59:59 GMT

signature

Not data — an HMAC-SHA256 fingerprint of header.payload, made with the server's SECRET_KEY. We can't decode it, and without the key we can't forge it.

We just read the header and payload with no key at all — exactly like the notebook's peek(). The lesson: a JWT is signed, not encrypted. Never put secrets in the payload; the signature only proves who minted it and that nobody changed it.

The guard: five lines that protect every route

oauth2 = OAuth2PasswordBearer(tokenUrl="login")   # "tokens arrive in the Authorization header"

def get_current_user(token: str = Depends(oauth2)) -> str:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])   # signature + expiry
    except jwt.PyJWTError:                                                # any failure -> 401
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Could not validate credentials")
    return payload["sub"]


@app.post("/chat")
async def chat(req: ChatIn, user: str = Depends(get_current_user)):    # <-- PROTECTED
    #                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the ENTIRE auth layer.
    #  FastAPI runs the guard first. If it raises 401, this function never executes.
    result = await agent.run(req.message)
    return {"user": user, "reply": result.output}

One Depends(get_current_user) in the signature and the endpoint is secured — jwt.decode checks the signature and the expiry in a single call. Passwords, meanwhile, are never stored: only SHA-256 hashes live in the DB, and login compares fingerprints.

Walk the pipeline

The capstone ends by attacking its own API. Step through the four requests the notebook fires at the running server:

HTTP 401

→ request

requests.post(f"{BASE}/chat", json={"message": "hi"})

← response

401 Unauthorized
{"detail": "Not authenticated"}

The OAuth2PasswordBearer dependency finds no Authorization header, so FastAPI rejects the request before your /chat code ever runs.

step 1 / 4

That's the entire course, end to end

A raw JSON-RPC dictionary became an MCP server, grew a brain, learned to delegate, got quantized onto your own hardware — and now sits behind an authenticated production API. The 33-minute capstone video codes this lesson live, from an empty Colab cell.

Go to the course