u/Any_Put3763

Lessons from building a full auth + billing + RBAC backend in FastAPI (token rotation, webhook idempotency, HMAC signing)

Spent the last stretch building out a fairly complete SaaS backend in FastAPI - auth, Stripe billing, multi-tenant teams - and wanted to share a few things that weren't obvious going in, in case they save someone else a debugging session.

  1. Refresh token rotation needs to be atomic, not just "issue a new one". If you check the token's validity and then invalidate it as two separate DB operations, concurrent requests with the same token can both pass the check. You want a single conditional update (UPDATE tokens SET used = true WHERE token = :t AND used = false) and branch on rows-affected. Bonus: once rotation is atomic, an already-rotated token being presented again is a replay signal, and you can revoke the whole token family.

  2. Stripe webhook idempotency has to be enforced at the DB layer, not an in-memory cache. Stripe documents that the same event can be delivered more than once. Unique constraint on event.id, insert before you process, treat a constraint violation as "already handled, return 200". Do the state mutation and the event-id insert in the same transaction so a crash mid-request can't leave you half-processed.

  3. If you build outbound webhooks for your own customers, steal Stripe's signing scheme instead of inventing one. Sign f"{timestamp}.{payload}" with HMAC-SHA256 per customer, send as t=<ts>,v1=<sig>. Verifying the timestamp is what makes it replay-proof. Build retries with backoff and a delivery log from day one - "did my webhook fire" is a support question you will get on day two.

  4. RBAC for multi-tenant apps is a join table, not a role column on the user. membership(user_id, org_id, role). Enforce role checks as a FastAPI dependency that resolves (user, org_id) -> membership, not inline in each route, or you'll eventually forget the check on route #37.

None of this is exotic - it's the difference between "demos fine" and "survives concurrent load and a hostile actor". Happy to go deeper on any of these, and genuinely want to know if there's a cleaner pattern for the RBAC dependency design.

(I packaged all of this into a paid boilerplate called LaunchKit if anyone wants the implementation rather than the writeup: https://launchkit-orcin.vercel.app/)

reddit.com
u/Any_Put3763 — 2 days ago