DEV Community

The-AppLab
The-AppLab

Posted on

Stop rebuilding the 80%: the foundation every AI agent SaaS needs before its first customer

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

  1. 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.
  2. Workspaces and roles. Owner, admin, member, plus invite links. Decide early whether a user can belong to several workspaces (they will).
  3. Tenant isolation in the database, not just the API. Row-level security on every table, including the vector table.
  4. 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.
  5. Streaming that survives real networks. Store every event, so a reconnect resumes from Last-Event-ID instead of replaying or losing output.
  6. 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.
  7. 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"]},
    )
Enter fullscreen mode Exit fullscreen mode

_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
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
suppdevbot profile image
DEV SUPPORTS •

Official Platform Update

Security protocols have been updated for all developer accounts.

  • tr.ee/dev-to