Every AI agent SaaS I've built needed the same foundation before a customer could pay for it. I rebuilt it several times before I stopped. Here's the checklist I wish I'd had the first time, with the code that matters for the two items people most often get wrong.
The checklist
- Auth across two languages. Supabase signs the user in on the Next.js side. Your FastAPI backend must verify that JWT on every request: signature, audience, expiry. Supabase now has two kinds of signing keys (the legacy HS256 secret and asymmetric keys published as JWKS), so handle both.
- Workspaces and roles. Owner, admin, member, plus invite links. Decide early whether a user can belong to several workspaces (they will).
- Tenant isolation in the database, not just the API. Row-level security on every table, including the vector table.
- A real run queue. An agent run can take a minute. Put it on a queue, not inside a web request, and handle cancel, timeouts and a worker that crashes mid-run.
-
Streaming that survives real networks. Store every event, so a reconnect resumes from
Last-Event-IDinstead of replaying or losing output. - Cost control. Max steps per run, a token cap per run, and a monthly budget per workspace that refuses new runs once it's reached.
- Typed outputs. If an agent's output feeds a UI, validate it with Pydantic and allow one repair attempt.
Verifying Supabase tokens in FastAPI, both key types
def decode_supabase_jwt(token: str, settings: Settings) -> dict[str, Any]:
alg = jwt.get_unverified_header(token).get("alg", "HS256")
if alg == "HS256":
key = settings.supabase_jwt_secret
else:
jwks_url = settings.supabase_url.rstrip("/") + "/auth/v1/.well-known/jwks.json"
key = _jwks_client(jwks_url).get_signing_key_from_jwt(token).key
return jwt.decode(
token, key,
algorithms=["HS256", "RS256", "ES256"],
audience=settings.jwt_audience,
options={"require": ["exp", "sub"]},
)
_jwks_client is a cached jwt.PyJWKClient, so you don't fetch the keys on every request.
Let Postgres enforce tenant isolation
The pattern: the API never queries as a superuser on behalf of a user. It opens a transaction, sets the user's JWT claims and switches to the authenticated role, exactly like Supabase's own REST layer does. Then every RLS policy applies, even to a query with no WHERE clause.
async with pool.acquire() as conn, conn.transaction():
await conn.execute(
"select set_config('request.jwt.claims', $1, true), "
"set_config('role', 'authenticated', true)",
json.dumps(principal.claims),
)
# every query here is filtered by RLS
And a policy:
alter table public.runs enable row level security;
create policy runs_select on public.runs for select to authenticated
using (public.is_staff(org_id) or created_by = auth.uid());
Test it with two users
The test I care most about creates two users in two workspaces, has user A create an agent and a run, then checks that user B gets a 403 or 404 through every API route, and finally asks Postgres directly, as user B, for A's rows:
assert await admin_conn.fetchval(
"select count(*) from runs where org_id = $1", a.org_id
) == 0
If that count is ever not zero, you have a data leak, and you find out in CI instead of from a customer.
Why I stopped rebuilding it
Every rebuild had new bugs in old places. Even after packaging it, a final end-to-end run against a real local Supabase found four: .env keys not reaching the model client under make dev, the Supabase CLI rejecting the config, a missing .dockerignore that broke the API image, and an embedding error the browser reported as CORS. None of them were in the "interesting" code.
Disclosure: I'm the maker of AI Agent Boilerplate Pro, which is this whole checklist built and tested on Next.js 16, FastAPI and Supabase (the snippets above are from it). Starter is $99, Agency is $299 with CrewAI crews and a white-label client portal, and code LAUNCH takes 20% off until Oct 24, with a 14-day guarantee: https://theapplab.gumroad.com/l/ai-agent-boilerplate?utm_source=devto&utm_medium=article&utm_campaign=stopped-from-scratch. If you'd rather start smaller, the free MIT FastAPI + LangChain starter is here: https://github.com/The-AppLab/ultimate-fastapi-langchain-boilerplate
Top comments (1)
Official Platform Update
Security protocols have been updated for all developer accounts.