auth · tutorial · alternative

Auth Without the 2 a.m. Pages: Google + Magic Links

One NextAuth config, per-database adapters, and a dev escape hatch that never ships. Zero Clerk MAU lock-in.

A sleeping soldier beside his horse and dog under a tree, a lithograph by Carle Vernet
A sleeping soldier beside his horse and dog under a tree, a lithograph by Carle Vernet · Photo: Carle Vernet, CC0, via Wikimedia Commons

Why Clerk and Auth0 bills shock founders at 10k MAUs

Authentication in modern web apps is frequently treated as a managed SaaS purchase. You drop in Clerk or Auth0, enjoy a slick 10-minute integration, and forget about it.

Until your product hits product-hunt or a viral tweet, and your "Monthly Active Users" counter crosses the free tier threshold. Suddenly:

MetricBoringPush (Google + Magic Links)Clerk / Auth0
Base Price$0 (Runs on your app)$25–$35/mo
Over 10k MAUs$0 (No user count tax)~$0.02 – $0.05 per active user ($200–$500/mo)
User Data LocationYour own DB (db/app.db or Supabase)Third-party cloud silo
Offline / Local DevLocal test bypass ([email protected])Requires cloud sync & API keys
Migration CostZero (Standard SQL user records)High (password hashes & tokens locked)

The math is simple: for an indie SaaS or bootstrapper, paying hundreds of dollars a month just to let people log into a database you already own is a tax you do not need to pay.

The three auth states that actually matter

Authentication does not need 15 microservices. For a SaaS, there are only three states that matter:

  1. Logged out: Show public marketing, docs, and the login button.
  2. Logged in (Free): Allow access to free boards, profile editing, and read-only features.
  3. Logged in (Paid): Grant full access to premium tools, APIs, and exports.

BoringPush compresses this into one NextAuth configuration that delegates persistence to whichever database you choose.

Instead of coupling your user table to a proprietary vendor schema, BoringPush provides identical authentication adapters across all three in-scope databases:

// Sessions are stored in stateless JWT tokens:
// No database lookups on hot route navigations
callbacks: {
  async session({ session, token }) {
    if (token?.sub) {
      session.user.id = token.sub;
    }
    return session;
  }
}

Because verification tokens and session identifiers map to plain tables across SQLite, MongoDB, and Supabase, you can switch your production database from a single-file SQLite database to a managed Supabase Postgres instance with one database setting, without breaking existing user identities, provided you move the user rows.

The dev escape hatch: test paywalls without real emails

The most frustrating part of developing auth flows is waiting for magic link emails or configuring OAuth redirect URIs for localhost:3000.

BoringPush includes a built-in development bypass that is completely stripped from production builds:

# In development only (NODE_ENV=development):
test@local.dev   # Signs in immediately as a free user
paid@local.dev   # Signs in immediately as an active lifetime paid user

No OAuth secrets, no third-party network calls, and no waiting for an inbox refresh. You can test gated routes and paywalls offline from an airplane. When you build for production (npm run build), the bypass is unreachable.

Two guards that prevent auth disasters

  1. Fail-Closed Admin Allowlist: Admin permissions live in a single list of emails in your settings. If the list is missing or empty, admin access is denied to everyone by default. It can never accidentally grant access to the public.
  2. Zero Redirect Loops: Stale browser sessions (where a valid session cookie exists but the user row was deleted in the database) safely redirect to /auth. The auth page only redirects inward when the user record actually exists in the active database engine, preventing infinite browser bounce loops.

FAQ

Can I add GitHub or Twitter login?

Yes. NextAuth supports 50+ OAuth providers. Add the credentials to .env.local and add the provider object into providers in the auth config.

Are JWT sessions safe for paywalls?

Yes. Fast navigation checks use the JWT claims, while money-sensitive endpoints (like license creation, Stripe portal access, and admin actions) run a fast indexed database lookup on User.findById(session.user.id).

How do I export my users if I ever leave?

Your users live in your own database table (users). You can export them with a single SQL query (SELECT * FROM users;) or SQLite dump at any time. No vendor export approvals needed.

+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)

The Agent-First Shipping Kit

Command from your phone.

Built so autonomous AI agents build what you want without breaking what's already paying you.

$199

Get BoringPush

One-time payment · Lifetime updates · Unlimited projects.

Keep reading