auth · tutorial
One Auth Flow, Three Databases, Zero Forced Logouts
Same login on all three databases: identical user rows, adapter per engine, sessions that survive the switch. The parity pattern inside.

Auth welded to one database
The app starts on SQLite.
Fast.
Local.
Perfect.
Users accumulate.
Then scale, a co-founder or compliance demands Postgres, and auth is welded to SQLite-isms.
Integer row IDs as user IDs.
Session lookups that assume file locking.
Migration becomes surgery on the beating heart.
User IDs change shape.
Sessions invalidate everywhere.
Everyone logs in again on cutover day.
A database decision, supposed to be infrastructure, becomes a user-facing event.
Auth should never know which engine runs.
Same rows.
Same actions.
Portable humans.
Welded or portable
| Moment | Welded to one database | Portable |
|---|---|---|
| Adding a second engine | Rewrite the adapter and the IDs | Write one adapter |
| Switching engines | A global forced re-login | Sessions can survive, if IDs carry over |
| User ID shape | Row ID, ObjectId or UUID | Opaque text everywhere |
| An agent adds a field | One engine, so drift | Every engine in the same change |
| A big scale-up | A panic migration | Move the rows, change one setting |
Welded auth fails at the worst moment. Three reasons.
ID shapes diverge silently. SQLite integers, Mongo ObjectIds, Supabase UUIDs. Sessions, foreign keys and URLs all embed the shape. Opaque text IDs, generated once and never interpreted, cross every engine unchanged.
Forced re-logins burn trust. A cutover-day mass logout reads as a breach, an outage or incompetence. Signed sessions validate by signature with no database lookup, so a flip does not by itself invalidate live logins, provided user IDs carry over.
Agents entrench the first engine. Every session without a parity rule deepens one engine's idioms. The longer that goes on, the more expensive freedom gets.
What to do
Fix the user record identically on every engine.
An opaque text ID.
A unique email.
A flag for paid access.
A plan type.
Same names, same types, same constraints, decided once per entity.
Email is unique everywhere. Identity must be singular across engines, or merges haunt you.
Give each engine its own sign-in adapter, behind the same behavior.
The auth library talks to the adapter and never to a database directly. It cannot tell which one runs. New fields land on all three in the same change, or the change is incomplete.
Keep identity in the session. Check the user on each guarded request.
The session carries only the user's ID. A guard reads that user's row to decide access. Signed sessions validate without a lookup, and one indexed read decides what the person may do.
Moving engines becomes: move the rows, change the database setting, and users keep shopping.
The engine-flip drill
| Check | Action | Pass looks like |
|---|---|---|
| Row parity | Compare the three user definitions | Same fields, same constraints |
| Behavior parity | Run the same sign-in on each engine | The same result everywhere |
| Live sessions survive | Flip the setting on staging mid-day | No forced re-logins |
| A new field | Add one on all engines | One change, every engine |
Drill the flip on staging every so often. A portability claim never rehearsed is marketing. Rehearsed, it is architecture.
The cost
Parity triples the adapter code. Three small pieces instead of one. Opaque IDs also give up native conveniences, like autoincrement ordering.
Pay it once, up front. Freedom compounds across every later decision.
Know this first
Parity triples the adapter code. Opaque IDs give up native conveniences.
That cost is paid once, up front.
Why BoringPush is the way to do this
People that move.
A user is the same record on SQLite, MongoDB and Supabase. BoringPush ships a sign-in adapter for each.
Switch engines. Your users stay.
FAQ
Does portable auth slow down logins?
No. Signed sessions validate with a signature check, no query.
The guard's single indexed read costs microseconds on every engine. Users cannot tell engines apart by latency; only your hosting bill can.
How do Supabase row-level policies fit?
BoringPush talks to Supabase from the server with a privileged key, so access rules live in the app's guards. Row-level policies are extra armor you can add on top, but they are not what the kit relies on.
What migrates passwords if there are none?
Nothing. Passwordless sign-in (Google plus magic links) means no credential material exists to move. OAuth identities re-link by email on the first login after a flip, and magic links work against migrated rows right away.
Should sessions store the engine name?
No. Engine-blindness is the feature. Log the engine below the adapter if you need it for debugging, never inside tokens.
How do I fix drift when one engine falls behind?
Freeze writes to the drifted field, export all three, compare them with a program rather than by eye, and write one convergence change per engine. Do it on staging first, and prevent the next drift with regular comparisons.
When is single-engine auth fine?
When you are sure the engine will never change: a Supabase-only shop, a stable SQLite-forever solo, or a prototype with a disposal date. The honest test: will I still run this engine in three years, guaranteed? If you hesitate, keep parity.
Keep reading
- Next.js Auth in an Afternoon: Google + Magic Links — the login doors this parity serves
- JWT Sessions That Bounce You Forever (10-Line Fix) — the token shape surviving flips
- requirePaidUser(): One Boolean, Zero Paywall Holes — the guard reading portable rows
+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)
Command from your phone.
Built so autonomous AI agents build what you want without breaking what's already paying you.
- Built-in analytics — cookie-free mode, $0 forever, no PostHog bill
- SQLite ↔ Supabase: one setting, same field names
- Full digital marketplace & feedback boards included
- 3 payment rails: Stripe, LemonSqueezy & Dodo Payments
- Agent fences: lint gate, read-only data access, auto-rollback
- Ship from your phone via SSH & WireGuard/Tailscale in under a minute
$199
Get BoringPushOne-time payment · Lifetime updates · Unlimited projects.
Keep reading


