AI-generert innhold
/Oskar Ydstebø

Supabase Row Level Security: Komplett guide for sikre Next.js-apper

supabaserlssikkerhetdatabasenextjs

> TL;DR

Row Level Security (RLS) i Supabase er en PostgreSQL-funksjon som kontrollerer datatilgang på radnivå. Hver tabell får policies som definerer hvem som kan lese, skrive, oppdatere og slette data. Med RLS trenger du ikke bygge autorisasjonslogikk i applikasjonen — databasen håndhever reglene selv. Det er obligatorisk for alle tabeller i en Supabase-app som er eksponert mot klienten.

De fleste sikkerhetsbrudd i webapplikasjoner skyldes ikke sofistikerte angrep. De skyldes at noen glemte å sjekke om brukeren faktisk har tilgang til dataen de prøver å hente.

Row Level Security (RLS) i Supabase løser dette problemet på det mest fundamentale nivået: i selve databasen. Det er en grunnleggende del av Supabase Auth-oppsettet vi anbefaler.

Hva er Row Level Security?

RLS er en PostgreSQL-funksjon som lar deg definere hvem som kan se og manipulere hvilke rader i en tabell. I stedet for å skrive if (user.id === row.owner_id) i hver eneste API-rute, definerer du regelen én gang i databasen.

Uten RLS (usikkert)

// API-rute uten RLS — du MÅ huske å sjekke tilgang
app.get('/api/documents', async (req, res) => {
  const documents = await db.query('SELECT * FROM documents WHERE owner_id = $1', [req.user.id]);
  // Hva om du glemmer WHERE-leddet? All data lekker.
  res.json(documents);
});

Med RLS (sikkert)

-- Databasen håndhever reglene — umulig å glemme
CREATE POLICY "Users can only see own documents"
ON documents FOR SELECT
USING (owner_id = auth.uid());

Med RLS aktivert kan klienten kalle supabase.from('documents').select('*') — og bare se sine egne dokumenter. Databasen filtrerer automatisk.

Hvordan sette opp RLS i Supabase

Steg 1: Aktiver RLS på tabellen

ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

Viktig: Når RLS er aktivert uten policies, har ingen tilgang til tabellen. Du må eksplisitt definere hvem som kan gjøre hva.

Steg 2: Definer policies

Det finnes fire operasjoner: SELECT, INSERT, UPDATE, DELETE.

-- Brukere kan lese egne dokumenter
CREATE POLICY "select_own_documents"
ON documents FOR SELECT
USING (owner_id = auth.uid());

-- Brukere kan opprette dokumenter (og blir automatisk eier)
CREATE POLICY "insert_own_documents"
ON documents FOR INSERT
WITH CHECK (owner_id = auth.uid());

-- Brukere kan oppdatere egne dokumenter
CREATE POLICY "update_own_documents"
ON documents FOR UPDATE
USING (owner_id = auth.uid())
WITH CHECK (owner_id = auth.uid());

-- Brukere kan slette egne dokumenter
CREATE POLICY "delete_own_documents"
ON documents FOR DELETE
USING (owner_id = auth.uid());

Steg 3: Test det

// Som bruker A
const { data } = await supabase.from('documents').select('*');
// Returnerer KUN bruker A sine dokumenter

// Forsøk på å lese bruker B sine dokumenter
const { data: other } = await supabase.from('documents').select('*').eq('owner_id', 'bruker-b-id');
// Returnerer tomt — RLS blokkerer

Vanlige RLS-mønstre

Team-basert tilgang

-- Brukere kan se dokumenter tilhørende sitt team
CREATE POLICY "team_select"
ON documents FOR SELECT
USING (
  team_id IN (
    SELECT team_id FROM team_members
    WHERE user_id = auth.uid()
  )
);

Rollebasert tilgang

-- Admin kan se alt, vanlige brukere ser kun egne
CREATE POLICY "role_based_select"
ON documents FOR SELECT
USING (
  owner_id = auth.uid()
  OR
  EXISTS (
    SELECT 1 FROM user_roles
    WHERE user_id = auth.uid()
    AND role = 'admin'
  )
);

Offentlig lesbart, privat skrivbart

-- Alle kan lese publiserte dokumenter
CREATE POLICY "public_read"
ON documents FOR SELECT
USING (published = true OR owner_id = auth.uid());

-- Kun eier kan skrive
CREATE POLICY "owner_write"
ON documents FOR INSERT
WITH CHECK (owner_id = auth.uid());

Vanlige feil med RLS

1. Glemmer å aktivere RLS

Uten ENABLE ROW LEVEL SECURITY er tabellen helt åpen. Supabase Dashboard viser en advarsel, men mange ignorerer den.

2. For brede policies

-- FEIL: Alle kan lese alt
CREATE POLICY "bad_policy"
ON documents FOR SELECT
USING (true);

3. Ytelsesproblem med subqueries

Komplekse policies med mange JOINs kan bremse queries. Bruk indekser:

CREATE INDEX idx_team_members_user_id ON team_members (user_id);
CREATE INDEX idx_documents_team_id ON documents (team_id);

4. Glemmer service role

Supabase sin service_role-nøkkel bypasser RLS. Den skal aldri eksponeres til klienten. Bruk den kun i server-side kode (Next.js API Routes / Server Actions).

RLS i praksis: Norske SaaS-produkter

Flere norske produkter bygget med Supabase bruker RLS som grunnmur for sikkerhet:

  • Boligposten — Eiendomsmeglere ser kun egne boliger og kampanjer. Team-basert tilgang lar meglerkontorer dele data internt.
  • FamPlan — Familiemedlemmer ser kun sin egen families data. Barn har begrenset tilgang styrt av foreldrepolicies.
  • Webagent.no — Multi-tenant SaaS der kunder kun ser egne prosjekter, fakturaer og AI-samtaler.

Alle disse bruker RLS kombinert med Next.js Server Components for å sikre at data aldri lekker — verken til andre brukere eller til klientsiden. For bedrifter som vurderer å bygge nettside med brukerdata, er RLS ikke valgfritt.

Oppsett i en Next.js-app

For en komplett gjennomgang av auth-oppsettet, se vår Supabase Auth guide.

Server Component (sikker)

// app/documents/page.tsx
import { createClient } from '@/lib/supabase/server';

export default async function DocumentsPage() {
  const supabase = await createClient();
  const { data: documents } = await supabase.from('documents').select('*');
  // RLS filtrerer automatisk — kun brukerens dokumenter returneres

  return (
    <ul>
      {documents?.map(doc => <li key={doc.id}>{doc.title}</li>)}
    </ul>
  );
}

Server Action (sikker)

'use server';

import { createClient } from '@/lib/supabase/server';

export async function createDocument(formData: FormData) {
  const supabase = await createClient();
  const { error } = await supabase.from('documents').insert({
    title: formData.get('title'),
    // owner_id settes IKKE her — RLS policy sikrer at auth.uid() brukes
  });

  if (error) throw new Error('Kunne ikke opprette dokument');
}

Sjekkliste for RLS

  • [ ] RLS er aktivert på alle tabeller eksponert mot klienten
  • [ ] SELECT, INSERT, UPDATE og DELETE policies er definert
  • [ ] service_role-nøkkel brukes kun server-side
  • [ ] Indekser er opprettet for kolonner brukt i policies
  • [ ] Policies er testet med ulike brukerroller
  • [ ] Ingen policy bruker USING (true) uten god grunn

Oppsummering

Row Level Security er ikke valgfritt — det er obligatorisk for enhver Supabase-app som håndterer brukerdata. Det er enklere å sette opp enn du tror, og det gir en sikkerhet som er umulig å oppnå med applikasjonslogikk alene.

Start med de fire grunnleggende policies (SELECT, INSERT, UPDATE, DELETE basert på auth.uid()), og utvid derfra etter behov.

For AI-drevne applikasjoner med automatisering, er RLS spesielt viktig for å beskytte brukerdata.

Fra teori til praksis

Denne artikkelen er en del av hjelp.dev-eksperimentet — der vi tester om AI-drevet innhold kan bygge ekte autoritet. Alt innhold er åpent og GEO-optimalisert.

Bygget av Webagent AS i Bergen.