---
name: nextjs-supabase-stripe-rules
description: "Apply security rules when an AI agent works on a Next.js App Router app that uses Supabase and Stripe: enable RLS with explicit grants and one policy per operation, keep the Supabase secret key and Stripe secrets server-only, verify Stripe webhook signatures on the raw body, make webhooks idempotent, and take prices and user ids from the server. Use when writing or reviewing Supabase migrations and RLS policies, Server Actions, route handlers, Stripe Checkout or webhook code, or before deploying such an app."
license: Free to use and adapt
metadata:
  publisher: Agent Prompts A to Z
  source: https://agentpromptsatoz.com/guides/nextjs-supabase-stripe-rules/
---
# Next.js + Supabase + Stripe rules

Free lite version of the paid kit. Follow these rules whenever you change Supabase SQL, Server Actions, route handlers, or Stripe code in this project.

## Supabase RLS and security

# Supabase: RLS and access rules

## Every table in an exposed schema (usually `public`)
1. `alter table ... enable row level security;` in the same migration that creates the table.
2. Set grants explicitly. Policies do not take grants away: `revoke all on table ... from anon, authenticated;` then grant back only what each role needs.
3. Write one policy per operation (select / insert / update / delete). Do not use `for all`.
4. Always name the role with `to authenticated` (or `to anon` only for data that is meant to be public). Never `to anon using (true)` on private data.
5. Wrap helpers: `(select auth.uid()) = user_id`, not `auth.uid() = user_id`.
6. `insert` needs `with check`. `update` needs `using` AND `with check`, plus a `select` policy.
7. Index every column a policy filters on (`user_id`, `team_id`).
8. Add `supabase/tests/<table>_rls.test.sql` (pgTAP) asserting allow and deny for anon and authenticated on each operation. Run `supabase test db`. A table is not done until it passes.

## Do not
- Base a policy on `user_metadata` (users can edit it). Use `app_metadata` for authorization data.
- Create a view without `with (security_invoker = true)` on Postgres 15+; views otherwise bypass RLS.
- Write a `security definer` function without `set search_path = ''` (and schema-qualified names), or put one in an exposed schema.
- Use `for all` or `using (true)` to "make the error go away". A `42501` error means check the grants first, then the policy.

## Keys
- Browser and per-request server clients use the publishable (anon) key plus the user's session. They are subject to RLS.
- The secret / `service_role` key bypasses RLS. It lives only in `lib/supabase/admin.ts` (`import 'server-only'`), is never `NEXT_PUBLIC_`, and is used only after you have checked the caller is allowed to do the thing.

Source: https://supabase.com/docs/guides/database/postgres/row-level-security

## Stripe checkout and webhooks

# Stripe: checkout and webhooks

## Webhook route (`app/api/stripe/webhook/route.ts`)
- `export const runtime = 'nodejs'`.
- Read the RAW body with `await request.text()`. Never `request.json()` before verifying: Stripe signs the exact bytes, and any change to the body breaks verification.
- Verify with the official library before doing anything else:
  `stripe.webhooks.constructEvent(rawBody, request.headers.get('stripe-signature')!, process.env.STRIPE_WEBHOOK_SECRET!)`
  On failure return 400. Never skip verification "for local testing". Use `stripe listen` (it prints a local `whsec_`).
- Do not set the tolerance to 0 (that disables the replay check). Keep the default (5 minutes).
- Return a 2xx quickly. Do slow work after the verified event is stored or queued.
- Events can arrive twice and out of order. Store `event.id` (unique constraint) and skip ones already processed. Do not rely on event order or on `created`.
- Subscribe only to the event types you handle.
- Fulfillment (granting access, marking an order paid) happens from the verified webhook, not from the `success_url` page.
- Use the service-role/admin Supabase client here (there is no user session), after verification.

## Checkout
- Create the session on the server with a Price ID from your own config. Never accept an amount, price or currency from the client.
- Put your user id in `client_reference_id` or metadata from the server session, never from request input.
- Keep `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` server-only. Test and live keys differ, and so do their webhook secrets.

Source: https://docs.stripe.com/webhooks

## Before deploying: check these 13 things (report PASS / FAIL / UNVERIFIED with file:line, change nothing)

1. No secrets in the browser: No Stripe secret, webhook secret or Supabase secret/service_role key behind a NEXT_PUBLIC_ variable or in a 'use client' file.
2. Secrets not committed: .gitignore covers .env*; no .env files or key-looking strings in tracked files.
3. RLS on every table: Every table created in an exposed schema has row level security enabled in its migration.
4. Grants and policy shape: Explicit revoke-then-grant; no 'for all'; no 'to anon using (true)' on user data; one policy per operation, with a role.
5. Policy correctness: insert has with check; update has using and with check; (select auth.uid()); nothing based on user_metadata.
6. Functions and views: security definer functions pin search_path and stay out of exposed schemas; views use security_invoker.
7. Secret client confined: The secret-key Supabase client is server-only and used only after an auth check or a verified webhook.
8. Server Actions and route handlers: Each one checks the user, checks ownership of the specific resource, and validates input.
9. Stripe webhook verification: Raw body, signature checked with the endpoint secret before anything else, 400 on failure.
10. Webhook reliability: event.id stored and duplicates skipped; no dependence on event order; fulfilment driven by the webhook.
11. Money and identity from the server: Price IDs and user ids come from the server, never from the request body.
12. Tests exist and aren't rigged: pgTAP allow/deny tests per table; webhook tests for good, bad and duplicate; no .skip or placeholder assertions.
13. Framework patch level and image safety: next at 16.4.1+ (16.x) or 15.5.28+ (15.x), sharp 0.35.5+, lockfile committed, no SVG uploads, images.dangerouslyAllowSVG not true, tight images.remotePatterns. Run npm ls next sharp and npm audit yourself.

## Security patching and install hygiene (checked against official pages, 2026-10-23)

- Next.js: fixed in next@16.4.1 and next@15.5.28 (advisory of 2026-10-14, https://nextjs.org/blog/nextjs-security-update-october-14-2026); sharp 0.35.5 or later. Never downgrade next, sharp, react or react-dom to make a build pass.
- React Server Components (GHSA-xqc2-xf4c-x3h3 critical RCE, GHSA-h3gv-c5rc-p453 high DoS; https://github.com/react/react/security/advisories/GHSA-xqc2-xf4c-x3h3): affects react-server-dom-webpack, -parcel and -turbopack; patched in 19.0.9, 19.1.10, 19.2.9, 19.3.1. For a Next.js app, take the Next.js fix.
- Node.js (security releases of 2026-10-21, https://nodejs.org/en/blog/vulnerability/october-2026-security-releases): fixed in 22.23.4 (LTS), 24.22.1 (LTS), 26.12.1 (Current). Node 20 and older are end-of-life and get no fix. Run node -v and check CI, Dockerfile and host settings; report FAIL and propose an upgrade, do not change the Node version yourself.
- Install from the lockfile with npm ci. Use --ignore-scripts for new or untrusted packages. Do not add, upgrade or swap a dependency without asking the human first; show its preinstall/install/postinstall scripts and pin an exact version. npm provenance does not prove a package is safe.
- npm incident: tensorlake@0.5.144 (2026-10-08, safe 0.5.143) stole tokens on install. If a package looks malicious: stop and report, and do NOT revoke or rotate any GitHub token yourself. The vendor advisory (https://github.com/tensorlakeai/tensorlake/security/advisories/GHSA-8g63-53c9-65j2) says the malware's gh-token-monitor service deletes the home directory if the stolen GitHub token is revoked, so the human removes that service first.
- Run npm ls next sharp and npm audit before every deploy, quote the real output, and treat a high or critical framework advisory as blocking. These versions will be superseded: re-read the official pages.

You did not run any tests during this review; tell the human to run `supabase test db` and the unit tests.

## Source

Full page: https://agentpromptsatoz.com/guides/nextjs-supabase-stripe-rules/ . Paid full kit (optional, bought by the user): https://payhip.com/b/Pdm5J
Docs these rules are based on: https://nextjs.org/docs/app/guides/data-security , https://supabase.com/docs/guides/database/postgres/row-level-security , https://docs.stripe.com/webhooks
