Public reference implementation · No client data

Membership SaaS architecture with enforceable boundaries

An inspectable design reference for a paid listing product built with Next.js App Router, Supabase and a subscription provider. It is not presented as a shipped client project.

Download the one-page technical capability brief (PDF)

Next.js server rendering
Supabase Auth + RLS
Idempotent webhooks
Entitlement tests

System boundary

The browser may request checkout and render authorized data, but it cannot author payment state. A server-only handler verifies signed billing events and writes a normalized membership state. Supabase RLS treats that state as the source of truth.

1 · Browser

Requests checkout; never writes entitlement

2 · Provider

Signs subscription lifecycle events

3 · Route handler

Verifies, deduplicates and normalizes

4 · Postgres + RLS

Enforces the access decision

Security invariants

  1. A browser cannot grant or extend its own paid access.
  2. A signed-in user can read only their own membership record.
  3. Full listing access requires a database-proven active entitlement.
  4. Replayed or out-of-order billing events cannot apply a transition twice.
  5. Administrative imports and service-role operations never execute in the browser.

Minimal data model

Keep provider payload evidence separate from the compact projection used for access decisions.

profiles(id, role)
memberships(user_id, provider_customer_id, status, access_until, updated_at)
billing_events(provider_event_id UNIQUE, event_type, received_at, payload_hash)
listings(id, visibility, title, teaser, private_body, seller_contact)
listing_imports(id, actor_id, source_hash, status, created_at)

RLS entitlement decision

Never copy a client-controlled “isPaid” flag into a policy. Verify server-owned state and its entitlement window.

exists (
  select 1 from memberships m
  where m.user_id = auth.uid()
    and m.status in ('active', 'trialing')
    and m.access_until > now()
)

Public previews should use a restricted view or column set. Hiding full-row fields with CSS is not access control.

Webhook transition rules

  1. Read the raw body and verify the provider signature.
  2. Insert the provider event ID under a unique constraint.
  3. Acknowledge duplicates without mutating state.
  4. Validate the subscription identity before applying a transition.
  5. Update the membership projection in the same transaction.
  6. Record a safe audit result without secrets or full payment payloads.

Authorization test matrix

ActorAllowedMust be denied
AnonymousPreview fieldsFull listing and seller data
Signed in, unpaidAccount and previewsPaid listing content
Active memberPaid contentOther users' account rows
Expired memberAccount and previewsPaid listing content
AdministratorExplicit admin operationsRaw payment secrets

Definition of done

Evidence and limitations

The public Supabase RLS leak fixture demonstrates cross-user isolation tests and remediation. This page extends that reasoning to membership billing; it does not claim a client deployment or production transactions.

See the RLS testing guide, staging and CI blueprint, security audit and engineering evidence portfolio.

Review all 20 frontend starters in one bundle

20 editable frontend starters with live demos are $79 — one payment, no subscription.

View the Bundle — $79