1
0 Comments

I shipped a freemium SaaS with a paywall that wasn't a paywall

A few months ago I launched SkillVault (skillvault.fr), a French freemium platform for digital certification prep (Google Ads, Meta Blueprint, AWS, HubSpot, Salesforce, etc.).

Yesterday I discovered my "paywall" was decorative. Three clicks in the browser dev tools and anyone could access all the Pro content for free.

Here's what was wrong, and how I fixed it in 5 phases over 2 days.

The damage

  • 120 QCM answer explanations sitting in plain HTML
  • Fake auth with prompt() accepting any email
  • isPro = true could be set from the browser console
  • "Free questions used" counter in localStorage (resettable)
  • No server-side check anywhere

My paying customers were paying for content available to everyone for free.

Phase 1: Supabase table with Row-Level Security

Created a qcm_questions table with an RLS policy that only allows reads for users where profiles.plan IN ('pro_monthly', 'pro_lifetime') AND the plan hasn't expired. The DB itself enforces the rule, no application code needed.

Phase 2: Extract and migrate

Python script that parses 12 HTML files, extracts the 120 answer explanations, uploads to Supabase via REST API with the service_role key. Idempotent, dry-run mode first.

Phase 3: Real authentication

Built a centralized auth.js module. Magic Link via Supabase (real verified emails), JWT session, plan check from DB, automatic expiry handling. No more prompt() faking accounts.

Phase 4: Runtime hydration

Pro users: explanations fetched from Supabase on page load (1 request per session, cached). Non-Pro users: shown a "Pro lock" message instead. Optimization: skip the request entirely for non-Pro to save Supabase quota.

Phase 5: The moment of truth

Python script that strips all explanations from HTML. Before: 27 KB of premium content visible via Ctrl+U. After: zero. The paywall finally became real.

The acid test

Incognito window, Ctrl+U, search "explain:". Before: 120 instances with full content. After: 120 empty strings.

Same logic applied to flashcards

While I was at it, I migrated all 144 flashcards to Pro-only. No card data in HTML at all. Non-Pro users see a "Premium content" wall, Pro users get them rendered from Supabase.

Lessons learned

  1. Pragmatic > perfect. I kept the correct answer index (0-3) in HTML so free users can still see if they got it right. I only moved the explanation (the actual pedagogical value) to Supabase. Real attackers can know the right answer, but not why it's right. The "why" is what users pay for.

  2. PostgreSQL RLS beats application-level auth checks. Define the policy once, enforced on every query, impossible to bypass from the frontend.

  3. Client-side validation is UX, not security. My old isPro JavaScript variable was 2 seconds away from being bypassed. Lesson learned the hard way.

  4. Idempotent migration scripts save your life. Every patch script I wrote can be re-run with zero side effects. No fear of partial deploys, no fear of running twice by mistake.

  5. The 5-minute test that should be mandatory: Open your product in incognito. Hit Ctrl+U. Search for any keyword that should be "premium-only". If you find it, you have my old problem.

Tech stack

Static HTML/CSS/JS on a 4€/month Hetzner VPS. Supabase free tier for auth + DB. Stripe for payments. n8n for webhooks. Total infra cost: under 10€/month.

The awkward part: my paying customers were paying for fully exposed content. I'm now wondering how many indie SaaS out there have the same problem and don't know it.

If you want to peek at the result: skillvault.fr (French only for now). Happy to share any of the Python migration scripts if useful.

on May 11, 2026
Trending on Indie Hackers
I've been building for months and made $0. Here's the honest psychological reason — and it's not what I expected. User Avatar 177 comments 7 years in agency, 200+ B2B campaigns, now building Outbound Glow User Avatar 65 comments This system tells you what’s working in your startup — every week User Avatar 53 comments 11 Weeks Ago I Had 0 Users. Now VIDI Has Reviewed $10M+ in Contracts - and I’m Opening a Small SAFE Round User Avatar 46 comments The "Book a Demo" Button Was Killing My Pipeline. Here's What I Replaced It With. User Avatar 29 comments My AI bill was bleeding me dry, so I built a "Smart Meter" for LLMs User Avatar 18 comments