TLDR: Deploy internal tools ungated. Hide them behind Vercel Deployment Protection. Add the auth gate only after you've verified the thing actually works on prod.

the setup

I was building an affiliate payout deduplication tool — an internal tool for an ecommerce business's affiliate team to dedup EPL payouts from our webinar registration platform (which tracks affiliate-referred registrants).

Classic internal tool. Small team. Sensitive data, but not public-facing.

And I had a decision to make before I deployed it: do I wire up Google OAuth right now, or later?

I've done the auth-first thing plenty of times. You set up the OAuth client in GCP (Google Cloud Platform), add GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to your env, wire ALLOWED_EMAILS, set AUTH_URL, update the redirect URI in the GCP console… and none of that is the thing you're trying to build.

Then you push it live and it still doesn't work because the redirect URI is wrong, or the session doesn't persist right, or your scopes are off. You've burned real time on auth before you've proven the core feature does anything useful.

the wall i kept hitting

This bit me hard on a practice management system I built for a law firm client.

The practice management system had Google login + Supabase session gates on every route. When I needed to verify a small change, I spun up next dev locally. Localhost /matters/* 307-redirected straight to /login. No Supabase session. OAuth redirect allowlists don't include localhost:3000.

Totally stuck.

The fix: push to main, let Vercel autodeploy, test prod directly via Arc CDP (my browser automation tool). That works instantly. But I'd wasted time fighting the auth gate instead of verifying the feature — the auth gate was the obstacle, not the thing I was trying to check.

the pattern that clicked

So for the affiliate payout tool I did it differently.

I deployed the whole app behind Vercel Deployment Protection — which means only people with Vercel access can even reach the URL. That's your outer gate. Enough for an internal tool in early flight.

Then I added an AUTH_ENABLED env var, defaulting to unset. The Google sign-in flow is wired up and READY — but it's off. When I'm ready to flip it on: set AUTH_ENABLED=true, add the Google credentials, add ALLOWED_EMAILS, done.

Ship first. Verify the thing works on real prod data. Then layer in auth.

the gotcha you can't skip

A few days later, during a security review of all the ecommerce business tools, we found the hole in this pattern.

The app-level auth gate protects pages. But server actions don't inherit that gate automatically.

Five server actions in the affiliate payout tool were still callable without auth — even with Vercel Protection on, a determined person with the URL could've called them directly. We added requireAuth() to every mutating action. And we made empty ALLOWED_EMAILS fail-closed (deny all) instead of fail-open (allow all).

So: ungated-first is fine. Just guard your server actions separately — they don't know your page-level auth exists.

why this matters to me

Every hour spent fighting OAuth redirect URIs and session state before the core feature is proven is an hour I didn't spend proving the core feature.

Vercel Deployment Protection is a real gate. Lean on it early. Ship the thing. See it work on prod data. Then add the auth layer you actually need.

Auth second isn't a security shortcut — it's a sequencing discipline. You can always tighten it. You can't get back the time you spent on it before you knew the thing was worth securing.