# Next.js Auth in an Afternoon: Google + Magic Links

> Google login plus passwordless email links on NextAuth: one config, three databases, dev bypass included. The starter wiring inside.

Source: https://boringpush.com/blog/nextjs-google-oauth-magic-links-starter

## Auth eats the first month

Every SaaS needs login before it needs anything else.

And login is a swamp.

The OAuth dance.

Token storage.

Session cookies.

Magic-link deliverability.

"Logged in, but the paywall says no."

Two weeks, minimum, before a user can even see your product.

So founders rent Auth0 or Clerk at per-user prices that scale against them. Or they hand-roll token spaghetti that breaks at the first redirect.

Either way, the product waits while the gate gets built.

## Auth options for a solo Next.js app

| Option | Setup time | Monthly cost | Where data lives |
|---|---|---|---|
| Hand-rolled tokens | Two weeks or more, bugs included | $0 | Yours, if correct |
| Auth0 or Clerk | Hours | Per user, growing | Theirs, export pain |
| Supabase Auth only | Hours | Tiered | Locked to Supabase |
| NextAuth, self-hosted | An afternoon | $0 | Your own database |

Self-hosted wins for indies. Three reasons.

**Per-user auth pricing taxes growth.** Every user costs margin before they pay you. That is exactly backwards for a funnel with thousands of free accounts.

**Lock-in strikes at migration time.** Switching vendors means forcing everyone to log in again, or running two systems for months. Own-database users move with a database setting and a row migration.

**Passwordless converts.** Many users would rather not create another password. Magic links convert them in one click. Google converts the rest in zero.

## What to do

Turn on both doors with two sets of keys.

Add your Google client details and an email-sending key to your production settings. Sign-in is live.

No passwords stored. No reset flows. No password hashing anywhere.

Sessions are signed tokens, so the hot path needs no database lookup, while users persist in whichever database you run.

Persist users the same way on every engine.

A user is the same record anywhere: an email as identity, a paid-access flag and a plan type. Orders and sign-in links refer to user IDs that never change shape.

Use the development logins for local paywall work.

On your own machine, a development-only login signs you in as a free user or a paying user with one click. No Google round trip. No email delivery.

Those logins exist only in development, never in production builds.

## The sign-in checklist

| Check | Action | Pass looks like |
|---|---|---|
| Google login | Click, consent, return | Signed in, user created |
| Magic link | Enter an email, click the link | The same session, no password |
| Paywall reads | A new user opens the dashboard | Sent to pricing, correctly |
| Database switch | Change the setting, sign in again | The same flow, a new engine |
| Dev logins | Use the local dev login | An instant session, offline |

Self-hosted auth means you own session security instead of renting it.

Keep your secrets private.

Rotate them now and then.

Keep the auth library updated.

The auth library handles the protocol. Your job is the secrets.

## What breaks most often

Google's console must list your exact callback address for each environment. Localhost and production differ by scheme and host. That mismatch is the top error.

Second is a consent screen stuck in testing mode, where only listed test users get through.

Both are console settings, never code. Open the provider dashboard before the editor.

## Know this first

You own session security now. Keep your secrets private and the auth library updated.

BoringPush ships no passwords, no multi-factor sign-in and no enterprise single sign-on.

## Why BoringPush is the way to do this

Two doors. Zero passwords.

Google sign-in and email links come wired, on any of three databases. Development logins let you test free and paid users offline.

Sign-in is done before you start.

## FAQ

### Does this work without passwords at all?

Yes, that is the point. Google never asks for your password, and magic links replace passwords with possession of an email inbox.

No credential database means no breach surface, no reset flows and no strength meters. People who distrust one door use the other.

### What breaks most often in Google setup?

The redirect address mismatch. Google's console must list your exact callback address for each environment, and localhost and production differ by scheme and host.

Second place is a consent screen stuck in testing mode, where only listed test users get through. Both are console settings, never code, so open the provider dashboard before the editor.

### How do magic-link emails avoid spam folders?

Verify SPF, DKIM and DMARC on your own domain, keep the sign-in address separate from marketing, and keep the message short and plain. The link is valid for 24 hours and works once. The deliverability post covers the DNS records in detail.

### Can I add passwords later for enterprise buyers?

You could add another provider behind the same user records, since users are keyed on email. BoringPush does not ship passwords or enterprise single sign-on, so that is work you would take on.

### Do sessions survive a database switch?

Sessions are signed tokens, so a switch does not by itself invalidate them. Move the user rows, change the setting and logins can continue, provided user IDs carry over.

### How long do sessions last?

Sessions use the auth library's long-lived defaults and refresh while people stay active. The kit does not add a shorter admin session or a revoke-all-devices switch. If you need those, you would add them yourself.

## Keep reading

- [Auth Without the 2 a.m. Pages: Google + Magic Links](/blog/auth-without-headaches) — the founding sketch this starter completes
- [redirect_uri_mismatch: The One-Character OAuth Fix](/blog/fix-redirect-uri-mismatch-google-oauth) — the error every setup hits once
- [requirePaidUser(): One Boolean, Zero Paywall Holes](/blog/requirepaiduser-paywall-one-boolean) — what login feeds downstream
