Next.js nad Supabase - bezpieczna eksploatacja
Runbook DevOps dla Next.js na Supabase: architektura, middleware, wzorce uwierzytelniania, rate limiting i integracja z Claude Code.
Po tym jak Supabase jako platforma backendowa działa stabilnie, na niej budowany jest właściwy stack aplikacyjny.
W wielu nowoczesnych projektach Next.js pełni rolę warstwy aplikacyjnej:
- Frontend Rendering
- Server Side Rendering
- Server Actions
- Route Handlers
- API Proxy
- Session Handling
Tym samym Next.js staje się faktycznie bramą backendową między przeglądarką a Supabase.
Najczęstszym błędem jest traktowanie tej warstwy jako “frontendu”, podczas gdy w rzeczywistości zawiera ona logikę serwerową z wysokimi uprawnieniami.
Ten runbook opisuje, jak bezpiecznie eksploatować Next.js nad self-hosted platformą Supabase.
Każda sekcja zawiera:
- Implementację
- warunek weryfikowalny
- Failure Scenario
W skrócie - Artykuł 2 z 6 serii DevOps Runbook
- Next.js jako osobny kontener odizolowany od Supabase
- Klucz service_role tylko w jednym pliku
- Middleware z getUser() przy każdym żądaniu
- Sprawdzenie ownership przed każdą mutacją
- Rate limiting na endpointach uwierzytelniania
Spis treści serii
Ta instrukcja jest częścią naszej serii runbooków DevOps dla self-hosted stacków aplikacyjnych.
- Supabase Self-Hosting Runbook
- Next.js nad Supabase - bezpieczna eksploatacja ← ten artykuł
- Supabase Edge Functions - bezpieczne wdrożenie
- Bezpieczna obsługa Trigger.dev Background Jobs
- Claude Code jako kontrola bezpieczeństwa w DevOps
- Security Baseline dla całego stacku
Artykuł 1 opisuje bazę platformową. Ten artykuł opisuje warstwę aplikacyjną powyżej.
Przegląd architektury
Browser (Client)
|
| HTTPS
|
Next.js App Layer
|
+-- @supabase/ssr
|
+-- service_role client
(tylko izolowane konteksty administracyjne)
|
Supabase Platform Layer
|
+-- Kong API Gateway
+-- GoTrue Auth
+-- PostgREST API
+-- Realtime
|
PostgreSQL Data Layer
|
+-- Row Level Security
Zasady podstawowe:
Browser -> komunikuje się tylko z Next.js
Next.js -> komunikuje się z Supabase
Supabase -> kontroluje dostęp przez RLS
Gdy przeglądarka komunikuje się bezpośrednio z wieloma usługami backendowymi, powstają niekontrolowalne granice bezpieczeństwa.
Część A - Decyzje architektoniczne
Te decyzje są rzadko zmieniane i stanowią fundament.
A1 - Next.js jako osobna usługa
Implementacja
Next.js działa jako osobny kontener.
services
nextjs-app
supabase-stack
postgres
Przykład docker-compose:
services:
nextjs-app:
build: ./app
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_SUPABASE_URL=http://kong:8000
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${ANON_KEY}
- SUPABASE_SERVICE_ROLE_KEY=${SERVICE_ROLE_KEY}
networks:
- internal
Next.js nie może działać wewnątrz stacku Supabase.
Warunek weryfikowalny
docker ps --format '{{.Names}}'
Oczekiwanie:
nextjs-app
supabase-kong
supabase-postgres
supabase-auth
Failure Scenario
Gdy Next.js działa w tym samym kontenerze co Supabase:
- przestrzeń procesów jest współdzielona
- sekrety znajdują się w tym samym środowisku
- skompromitowany serwer Next.js ma bezpośredni dostęp do wszystkich usług backendowych
A2 - Przeglądarka komunikuje się tylko z Next.js
Implementacja
Przeglądarka może widzieć tylko jeden publiczny URL.
https://app.example.com
Niedozwolone:
https://app.example.com:8000
https://app.example.com:5432
https://app.example.com:9000
Przykład firewalla:
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 8000 -j DROP
iptables -A INPUT -p tcp --dport 5432 -j DROP
iptables -A INPUT -p tcp --dport 9000 -j DROP
Warunek weryfikowalny
nmap -p 443,3000,5432,8000,9000 app.example.com
Oczekiwanie:
443 open
wszystkie inne filtered
Failure Scenario
Gdy Supabase Studio jest publicznie dostępne:
- pełny dostęp do bazy danych
- manipulacja schematem
- dostęp do bucketów Storage
A3 - Ustawienie Security Headers
Next.js pełni rolę bramy i musi ustawiać nagłówki HTTP Security.
Implementacja
next.config.js
const securityHeaders = [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=()" },
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
{
key: "Content-Security-Policy",
value: "default-src 'self'; img-src 'self' data: blob:; frame-ancestors 'none'"
}
]
Warunek weryfikowalny
curl -I https://app.example.com
Oczekiwane nagłówki:
X-Frame-Options
Content-Security-Policy
Strict-Transport-Security
Failure Scenario
Bez CSP:
- ataki XSS ładują zewnętrzne skrypty
- tokeny mogą być wykradzione
Zmienne środowiskowe
| Zmienna | Widoczność | Dozwolona lokalizacja | Ryzyko wycieku |
|---|---|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Klient | .env, kod klienta | Niskie (publiczny URL) |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Klient | .env, kod klienta | Średnie (ograniczony przez RLS) |
| SUPABASE_SERVICE_ROLE_KEY | Tylko serwer | lib/supabase/admin.ts | Krytyczne (omija RLS) |
| DATABASE_URL | Tylko serwer | .env (serwer) | Krytyczne (bezpośredni dostęp do DB) |
| TRIGGER_API_KEY | Tylko serwer | .env (serwer) | Wysokie (dostęp do kolejki zadań) |
Część B - Kontrole implementacji
Te zasady obowiązują przy każdej zmianie kodu.
B1 - Separacja zmiennych środowiskowych
Widoczne dla klienta:
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
Tylko serwer:
SUPABASE_SERVICE_ROLE_KEY
DATABASE_URL
TRIGGER_API_KEY
Warunek weryfikowalny
grep -r "NEXT_PUBLIC_" .env*
Sekrety nie mogą tam występować.
Build Leak Check
grep -r "SERVICE_ROLE" .next/
Oczekiwanie:
brak wyników
Failure Scenario
service_role w bundlu klienta oznacza:
- pełny dostęp do bazy danych
- RLS całkowicie nieskuteczny
Kto zna podstawy zarządzania sekretami, rozumie dlaczego ta separacja jest niezbędna.
B2 - Prawidłowa konfiguracja Supabase SSR Client
Server Client:
import { createServerClient } from "@supabase/ssr"
Serwer używa anon key, nie service_role.
Admin Client:
createClient(url, SERVICE_ROLE_KEY)
Tylko do zadań administracyjnych.
Warunek weryfikowalny
grep -rn "SERVICE_ROLE" app/
Oczekiwanie: tylko w
lib/supabase/admin.ts
Failure Scenario
Server Client z service_role:
- RLS jest całkowicie omijany
- wszystkie żądania mają uprawnienia administratora
B3 - Middleware dla Auth i Token Refresh
Middleware działa przed każdym żądaniem.
middleware.ts
Implementacja
const { data: { user } } = await supabase.auth.getUser()
Nie:
getSession()
Warunek weryfikowalny
grep getUser middleware.ts
Failure Scenario
Bez middleware:
- Token Refresh nie działa
- sesja wygasa po 1 godzinie
B4 - Wzorzec mutacji
Wszystkie mutacje przebiegają według tego samego schematu.
Auth
Input Validation
Ownership Check
Mutation
Logging
Przykład
const { user } = await supabase.auth.getUser()
if (!user) return
const post = await supabase
.from("posts")
.select("user_id")
.eq("id", postId)
if (post.user_id !== user.id)
throw new Error("forbidden")
Failure Scenario
Bez Ownership Check:
Statystyka: Zgodnie z badaniami GitHuba z 2024 roku ponad 23% wycieków sekretów w projektach webowych dotyczy kluczy service_role lub równoważnych tokenów backendowych.
deletePost(id)
Użytkownik może usunąć cudze dane.
B5 - Rate Limiting
Next.js nie ma wbudowanego Rate Limitingu.
Zalecane rozwiązanie:
Upstash Redis
Przykład
10 requests / minute / IP
Warunek weryfikowalny
for i in {1..20}
do curl -X POST /api/login
done
Oczekiwanie:
HTTP 429
B6 - Logowanie bez sekretów
Logi nie mogą zawierać:
JWT Tokens
service_role keys
emails
passwords
Warunek weryfikowalny
grep console.log app/
Część C - Eksploatacja
C1 - Aktualizacje zależności
Wydania Next.js są częste.
Cotygodniowa weryfikacja:
npm audit
npm outdated
Zalecane:
Renovate / Dependabot
C2 - Integracja Claude Code
Architektura:
Git Push
|
Kontrole deterministyczne
|
Raport bezpieczeństwa
|
Analiza Claude
|
Decyzja DevOps
Kontrole deterministyczne
grep service_role
grep NEXT_PUBLIC
npm audit
Analiza Claude
Claude weryfikuje:
- nowe Server Actions
- nowe API Routes
- wzorce Ownership
- Input Validation
- dryft architektury
Claude nie wykonuje żadnych zmian na produkcji.
Lista kontrolna wdrożenia
Przed każdym wdrożeniem sprawdzić:
[ ] Next.js działa jako osobna usługa
[ ] porty Supabase zamknięte zewnętrznie
[ ] Security Headers aktywne
[ ] brak service_role w bundlu klienta
[ ] service_role tylko w Admin Client
[ ] middleware.ts obecny
[ ] Server Actions weryfikują Auth
[ ] Ownership Checks zaimplementowane
[ ] Rate Limit logowania aktywny
[ ] npm audit bez krytycznych wyników
Podsumowanie
Next.js w nowoczesnym stacku nie jest frontendem, lecz uprzywilejowaną warstwą serwerową.
Bezpieczeństwo powstaje na trzech poziomach:
Architektura
Kontrole implementacji
bieżące audyty
Kombinacja kontroli bezpieczeństwa CI i analizy Claude Code wykrywa zarówno znane wzorce, jak i nowe zagrożenia.
Kto stosuje te zasady razem z architekturą Cert-Ready by Design, buduje weryfikowalne bezpieczeństwo zamiast audytów po fakcie.
Pobierz listę kontrolną audytu
Przygotowany prompt dla Claude Code. Prześlij plik na swój serwer i uruchom Claude Code w katalogu projektu aplikacji Next.js. Claude Code automatycznie sprawdzi wszystkie punkty bezpieczeństwa z tego runbooka i zgłosi ZALICZONY, OSTRZEŻENIE lub KRYTYCZNY.
claude -p "$(cat claude-check-artikel-2-nextjs-pl.md)" --allowedTools Read,Grep,Glob,Bash
Pobierz listę kontrolnąSpis treści serii
- Supabase Self-Hosting Runbook
- Next.js nad Supabase - bezpieczna eksploatacja ← ten artykuł
- Supabase Edge Functions - bezpieczne wdrożenie
- Bezpieczna obsługa Trigger.dev Background Jobs
- Claude Code jako kontrola bezpieczeństwa w DevOps
- Security Baseline dla całego stacku
Następny artykuł opisuje, jak bezpiecznie wdrożyć Supabase Edge Functions - bez budowania drugiej architektury backendowej.

Bert Gogolin
Dyrektor Generalny, Gosign
AI Governance Briefing
Enterprise AI, regulacje i infrastruktura - raz w miesiącu, bezpośrednio ode mnie.