Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Even at one user, you need authentication (so others don't get free credits) and a user model (so you can attribute usage, memory, billing). Don't roll your own auth — use a provider (Clerk, Supabase Auth, Auth0, Magic.link). Get it right early; auth migrations later are painful.
Day-1 auth: email + magic link or Google OAuth. Sessions via JWT cookies. Per-request middleware that injects user_id. Your DB has a users table; every other table foreign-keys to it. Tier in the user row (free / pro / enterprise) so feature gating is one SELECT. Don't store passwords. Don't bake your own JWT signing. Provider has done this for you.
Use these three in order. Each builds on the one before.
What's the minimum auth + user model for a Day-1 agentic API?
Walk me through JWT cookies + middleware. What does the middleware do per request?
Design a user model that supports tiers (free/pro/team/ent), per-tenant for B2B, and SSO for enterprise. What's the schema?
# FastAPI + Clerk (or any provider with a Python SDK)
from fastapi import FastAPI, Depends, HTTPException, Request
from clerk_backend_api import Clerk
app = FastAPI()
clerk = Clerk(bearer_auth=os.environ["CLERK_SECRET"])
async def get_current_user(request: Request) -> dict:
token = request.headers.get("Authorization", "").replace("Bearer ", "")
try:
session = clerk.sessions.verify_session(token=token)
user = clerk.users.get_user(user_id=session.user_id)
return {"id": user.id, "email": user.email_addresses[0].email_address, "tier": user.public_metadata.get("tier", "free")}
except Exception:
raise HTTPException(401)
@app.post("/api/chat")
async def chat(request: Request, user = Depends(get_current_user)):
# tier-gated features
if user["tier"] == "free" and not allow(user["id"], 50, 86400):
raise HTTPException(429, "free tier daily limit")
# ...
# Database
CREATE TABLE users (
id text PRIMARY KEY, -- match provider's user_id
email text UNIQUE NOT NULL,
tier text NOT NULL DEFAULT 'free',
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE traces (
id bigserial PRIMARY KEY,
user_id text NOT NULL REFERENCES users(id),
...
);
CREATE INDEX traces_user ON traces(user_id);
-- enable RLS for defense in depth
ALTER TABLE traces ENABLE ROW LEVEL SECURITY;
CREATE POLICY traces_owner ON traces FOR ALL USING (user_id = current_setting('app.uid'));python3 main.py