Open Developer Security Runbook • TIC Association

Next.js Production Auth Guard & Architecture Checklist

Eliminate shared cache session bleeding, Supabase RLS policy bypasses, and webhook replay vulnerabilities before deploying to production.

What is included in this 18-Point Security Resource:
  • Shared cache and Set-Cookie isolation preventing cross-user session bleeding in CDN and proxy layers.
  • PostgreSQL Row-Level Security (RLS) policies with strict tenant ownership verification.
  • Stripe webhook HMAC signature preservation and idempotent event processing.
  • Automated shell heuristic script to audit your codebase in seconds.
  • Documented trade-offs, security definer search_path lockdowns, and testing guidelines.
Read Full Runbook Below
100% Free Open Runbook (EUR 0.00). No email wall or credit card required.

Quick Audit: Run in Project Root

Execute this lightweight bash check to identify common configuration pitfalls across your environment and SQL files:

# 1. Search for leaked secret keys in public environment variables
grep -rnE "NEXT_PUBLIC_.*(SECRET|PRIVATE_KEY|SERVICE_ROLE)" . \
  --include="*.env*" --include="*.ts" --include="*.tsx" --include="*.js"

# 2. Check for raw JSON parsing inside Stripe webhook handlers
grep -rn "request.json()" app/api/webhooks/ \
  && echo "WARNING: Stripe webhooks must read request.text(), not request.json()"

# 3. Detect broad RLS policies that check auth without tenant filtering
grep -rnE "USING\s*\(\s*auth\.uid\(\)\s+IS\s+NOT\s+NULL\s*\)" . \
  --include="*.sql" && echo "WARNING: Policy checks login, but not tenant row ownership"

The 18 Production Security Invariants

Code Implementation Disclaimer: The reference implementation patterns and code snippets in this resource have been statically reviewed against official framework specifications (@supabase/ssr 0.5.x and Next.js 14/15 App Router) rather than executed as live automated test suites. Always verify integration behavior within your project's specific dependency versions and deployment environment.

Part 1: Authentication & Token Boundaries

1. Shared Cache Boundary & Set-Cookie Isolation

Failure Mode: An authenticated route handles token refresh and issues Set-Cookie headers (sb-<project_ref>-auth-token) while overriding cache protections with public caching directives (e.g. s-maxage). If an intermediate CDN or proxy caches such responses, it replays the Set-Cookie header to subsequent visitors. The recipient browser stores the token in its cookie jar as an HTTP cookie, causing session hijacking. Exploitation depends on whether intermediate caches capture responses with Set-Cookie.

Invariant: Always enforce Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate, Pragma: no-cache, and Expires: -1 across both successful (200) and unauthorized (401) responses. Retain refreshed or cleared cookies on outgoing responses, forward any anti-caching metadata, ensure external headers cannot weaken strict private cache directives, and verify that CDN edge rules do not override origin private cache headers.

2. Anon Key vs Service Role Key Boundary

Failure Mode: Prefixing SUPABASE_SERVICE_ROLE_KEY with NEXT_PUBLIC_ or importing it into client components.

Invariant: The Service Role key must strictly lack public framework prefixes and must never be referenced in client bundles. Only authenticated background workers should access this credential.

3. Middleware Token Refresh Handshake

Failure Mode: Validating JWT tokens in middleware without synchronizing refreshed session cookies back to both request and response objects.

Invariant: Middleware must synchronize updated cookies via the standard @supabase/ssr pattern to prevent session desynchronization.

4. Insecure Client-Side Metadata Trust

Failure Mode: Reading authorization roles directly from user.user_metadata.is_admin.

Invariant: Users can modify their own user_metadata via the client API. Authorization decisions must query database tables or cryptographically signed claims in app_metadata.

5. Long-Lived Streaming Expiration

Failure Mode: SSE or WebSocket connections that authenticate once on connection and never revalidate expiring tokens.

Invariant: Long-lived streams must enforce periodic token revalidation or a maximum connection lifetime ceiling (e.g. 1 hour).

Part 2: PostgreSQL Row-Level Security (RLS)

6. Mandatory Table RLS Enablement in Migrations

Failure Mode: Executing CREATE TABLE without ALTER TABLE ... ENABLE ROW LEVEL SECURITY;. PostgreSQL tables without RLS permit full read/write access via the anon key.

Invariant: Every newly created table must include explicit RLS enablement in the same migration file.

7. Strict Tenant ID Filtering in Policies

Failure Mode: Creating policies with USING (auth.uid() IS NOT NULL). This only verifies that a user is logged in, allowing any authenticated user to view records belonging to every other user.

Invariant: Policies must enforce row ownership: USING (auth.uid() = user_id) or join through a validated membership table.

8. Security Definer Search Path Lockdown

Failure Mode: Defining stored functions with SECURITY DEFINER without setting search_path = '', permitting schema hijacking by unprivileged callers.

Invariant: Always specify SET search_path = '' on SECURITY DEFINER functions, and qualify table references with explicit schema names.

9. Relational Join Protection

Failure Mode: Joining an RLS-protected table to an unprotected lookup table that reveals sensitive tenant relationships.

Invariant: Ensure all joined tables have appropriate RLS policies enabled.

10. Unprivileged Role Testing in SQL Console

Failure Mode: Testing RLS policies in the Supabase SQL editor while logged in as the administrative role postgres, which bypasses RLS.

Invariant: Always test policies using an unprivileged role context: SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.sub" = '<uuid>';.

Part 3: External Connectors & Webhook Integrity

11. Raw Webhook Body Parsing

Failure Mode: Calling await request.json() before Stripe HMAC signature verification. Parsing JSON and re-serializing it can mutate signed bytes (whitespace, property order, floating-point representations), breaking signature verification.

Invariant: Read the raw body as text using await request.text() before passing to stripe.webhooks.constructEvent().

12. Webhook Idempotency & Replay Defense

Failure Mode: Processing transactions without checking if event.id was already processed, resulting in duplicate credits or double charges on retry storms.

Invariant: Record incoming event.id in a deduplication table within a serializable transaction. If present, return HTTP 200 immediately without reprocessing.

13. Comprehensive Payment Lifecycle Handling

Failure Mode: Subscribing exclusively to checkout.session.completed while ignoring refunds, disputes, and cancellations.

Invariant: Handle refund and dispute webhook events to revoke access immediately upon chargeback.

14. Fast Webhook Acknowledgment

Failure Mode: Executing heavy background tasks inside the webhook HTTP response handler, causing gateway timeout retries.

Invariant: Return HTTP 200 within 500ms; dispatch background processing to a durable task queue.

Part 4: Operational Safeguards

15. Secret Key Naming Discipline

Failure Mode: Accidental public naming of private credentials (e.g. NEXT_PUBLIC_STRIPE_SECRET_KEY).

Invariant: Automated CI linters must reject any private key name containing public framework prefixes.

16. Explicit CORS Whitelisting

Failure Mode: Permitting wildcard * origins while accepting session credentials or cookies.

Invariant: Specify exact, trusted origins for authenticated requests.

17. Shared Rate Limiting on Serverless Edge

Failure Mode: Using in-memory rate limiting counters in serverless functions, which reset on every cold start.

Invariant: Store rate limits in a centralized persistent store or Redis with atomic increments.

18. Verified Rollback Migrations

Failure Mode: Applying schema migrations without corresponding rollback procedures.

Invariant: Every forward migration must have a validated reverse migration script.

Request Async Architecture Review

Next Steps & Production Toolkits

Continue hardening your production stack with our companion developer tools:

Free Resource

TIC Anti-Fragility Scheme Check

Comprehensive 10-point systemic resilience audit for modern web stacks. Verify zero-point dependencies, automated fallbacks, and fail-safe defaults.

Get Free Scheme Check →
Flagship Catalog

TIC Tools Atlas (1,200+ Vetted Tools)

The definitive developer directory of battle-tested open source libraries, infrastructure architectures, and automation pipelines.

Explore Tools Atlas →