GDPR for softwareudviklere | Hvad du faktisk skal implementere
En udviklerfokuseret guide til GDPR-compliance. Dækker de tekniske krav, datahåndteringsmønstre og beslutninger på kodeniveau, du skal tage.
GDPR har været i kraft siden 2018, men de fleste udviklervejledninger fokuserer stadig på den juridiske teori. Denne guide er anderledes. Den dækker de tekniske krav, den kode, du skal skrive, og de arkitektoniske beslutninger, der gør compliance praktisk i stedet for smertefuld.
Hvis din software lagrer, behandler eller berører persondata for personer i EU, gælder dette for dig. Det er ligegyldigt, hvor din virksomhed er baseret.
GDPR-overblik for udviklere
Spring de 99 artikler over. Her er hvad GDPR betyder for din kodebase:
- Indsaml kun det, du har brug for. Gem ikke data “for en sikkerheds skyld.”
- Fortæl brugerne, hvad du gør med deres data. Og få deres tilladelse, når det er påkrævet.
- Lad brugerne tilgå, eksportere og slette deres data. Du har brug for API-endpoints til dette.
- Hold data sikre. Kryptering, adgangskontroller, audit-logs.
- Rapporter brud hurtigt. Du har 72 timer til at underrette myndighederne efter opdagelsen af et brud.
- Dokumenter alt. Dine behandlingsaktiviteter, dine sikkerhedsforanstaltninger, dine dataflows.
Det er det praktiske resumé. Resten af denne guide viser dig, hvordan du implementerer hvert krav.
De 7 vigtigste tekniske krav
1. Samtykkeadministration
Samtykke skal være frit givet, specifikt, informeret og utvetydigt. Forudafkrydsede bokse tæller ikke. Bundlet samtykke (“accepter alt”) tæller ikke. Tilbagetrækning skal være lige så let som at give samtykke.
Hvad du skal bygge
Et samtykkesystem kræver tre komponenter:
- Et samtykkeposter-lager. For hver bruger, spor hvad de samtykkede til, hvornår og hvordan.
- En samtykkekontrolmekanisme. Før behandling af data til et specifikt formål, bekræft at brugeren har aktivt samtykke.
- En tilbagetrækningsmekanisme. Lad brugerne tilbagekalde samtykke gennem dit UI, og stop behandling øjeblikkeligt.
Database-skema
CREATE TABLE user_consents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
consent_type VARCHAR(100) NOT NULL,
granted BOOLEAN NOT NULL,
granted_at TIMESTAMPTZ,
revoked_at TIMESTAMPTZ,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_user_consents_lookup
ON user_consents (user_id, consent_type, granted);
Gem den fulde historik. Slet eller overskriv aldrig samtykkeposter. Når en bruger tilbagekalder samtykke, indsæt en ny række med granted = false og sæt revoked_at. Det giver dig et audit trail.
Samtykke-tjek middleware
Her er en Express middleware, der tjekker samtykke inden behandling af en forespørgsel:
import { Request, Response, NextFunction } from "express";
import { db } from "./database";
interface ConsentRequirement {
type: string;
required: boolean;
}
function requireConsent(consentType: string) {
return async (req: Request, res: Response, next: NextFunction) => {
const userId = req.user?.id;
if (!userId) {
return res.status(401).json({ error: "Authentication required" });
}
const consent = await db.query(
`SELECT granted FROM user_consents
WHERE user_id = $1 AND consent_type = $2
ORDER BY created_at DESC
LIMIT 1`,
[userId, consentType]
);
if (!consent.rows[0]?.granted) {
return res.status(403).json({
error: "Consent required",
consentType,
message: `You must grant "${consentType}" consent to use this feature.`,
consentUrl: `/settings/privacy`,
});
}
next();
};
}
// Usage
app.post(
"/api/newsletter/subscribe",
requireConsent("marketing_emails"),
subscribeHandler
);
app.post(
"/api/analytics/track",
requireConsent("usage_analytics"),
trackHandler
);
2. Dataadgang og eksport (Ret til indsigt)
Brugere har ret til at anmode om en kopi af alle persondata, du opbevarer om dem. Du skal levere det i et alment brugt, maskinlæsbart format. JSON eller CSV fungerer fint.
Hvad du skal bygge
Et endpoint, der indsamler alle persondata for en bruger på tværs af alle tabeller og tjenester og derefter pakker det i en downloadbar fil.
interface DataExport {
exportedAt: string;
user: {
profile: Record<string, unknown>;
activity: Record<string, unknown>[];
consents: Record<string, unknown>[];
communications: Record<string, unknown>[];
};
}
app.get("/api/me/data-export", authenticate, async (req, res) => {
const userId = req.user.id;
const [profile, activity, consents, communications] = await Promise.all([
db.query("SELECT id, email, name, created_at FROM users WHERE id = $1", [
userId,
]),
db.query(
"SELECT action, metadata, created_at FROM user_activity WHERE user_id = $1 ORDER BY created_at DESC",
[userId]
),
db.query(
"SELECT consent_type, granted, granted_at, revoked_at FROM user_consents WHERE user_id = $1 ORDER BY created_at DESC",
[userId]
),
db.query(
"SELECT type, sent_at, subject FROM communications WHERE user_id = $1 ORDER BY sent_at DESC",
[userId]
),
]);
const exportData: DataExport = {
exportedAt: new Date().toISOString(),
user: {
profile: profile.rows[0],
activity: activity.rows,
consents: consents.rows,
communications: communications.rows,
},
};
res.setHeader("Content-Type", "application/json");
res.setHeader(
"Content-Disposition",
`attachment; filename="data-export-${userId}.json"`
);
res.json(exportData);
});
Dette endpoint skal dække alle tabeller, der indeholder brugerdata. Gennemgå dit skema grundigt. En manglende tabel betyder en ufuldstændig eksport, hvilket er en compliance-fejl.
3. Ret til sletning (Retten til at blive glemt)
Brugere kan anmode om, at du sletter alle deres persondata. Du skal efterkomme dette, medmindre du har en juridisk forpligtelse til at beholde dem (som skatteoptegnelser eller forebyggelse af svindel).
Hvad du skal bygge
Et sletningsendpoint, der fjerner eller anonymiserer brugerdata på tværs af alle tabeller. Det er sværere, end det lyder, på grund af fremmednøglebegrænsninger og data, som andre systemer afhænger af.
app.delete("/api/me/account", authenticate, async (req, res) => {
const userId = req.user.id;
const client = await db.getClient();
try {
await client.query("BEGIN");
// Anonymize data that must be retained for business records
await client.query(
`UPDATE orders
SET customer_name = 'deleted', customer_email = 'deleted'
WHERE user_id = $1`,
[userId]
);
// Delete data that can be fully removed
await client.query("DELETE FROM user_activity WHERE user_id = $1", [
userId,
]);
await client.query("DELETE FROM user_consents WHERE user_id = $1", [
userId,
]);
await client.query("DELETE FROM communications WHERE user_id = $1", [
userId,
]);
await client.query("DELETE FROM sessions WHERE user_id = $1", [userId]);
// Anonymize the user record instead of deleting
// This preserves referential integrity
await client.query(
`UPDATE users SET
email = 'deleted-' || id || '@removed.invalid',
name = 'Deleted User',
phone = NULL,
address = NULL,
deleted_at = NOW()
WHERE id = $1`,
[userId]
);
await client.query("COMMIT");
// Trigger deletion in external systems
await Promise.allSettled([
emailService.deleteSubscriber(userId),
analyticsService.deleteUser(userId),
searchIndex.removeUser(userId),
]);
res.json({ message: "Account and personal data deleted" });
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
});
Vigtige beslutninger:
- Slet vs. anonymiser. Poster, der er nødvendige for bogføring (ordrer, fakturaer), bør anonymiseres. Slet alt andet.
- Eksterne systemer. Data sendt til tredjepartstjenester skal også slettes der.
- Timing. GDPR siger “uden unødig forsinkelse.” Gennemfør sletning inden for 30 dage. For de fleste systemer, gør det øjeblikkeligt.
4. Dataminimering
Indsaml og opbevar kun de data, du faktisk har brug for til et angivet formål. Hvis du beder om et telefonnummer, men aldrig ringer til brugerne, bør du ikke indsamle det.
Praktiske regler
- Gennemgå hvert formularfelt. For hvert felt, spørg: “Hvilken specifik funktion går i stykker, hvis vi fjerner dette?” Hvis svaret er intet, fjern det.
- Sæt opbevaringsperioder. Behold ikke data for evigt. Definer hvor længe hver type data er nødvendig, og slet den derefter automatisk.
- Minimer logning. Fjern persondata fra logposter. Log bruger-ID’er, ikke navne eller e-mails.
-- Automatic data retention with PostgreSQL
-- Run this as a scheduled job (e.g., pg_cron)
DELETE FROM user_activity
WHERE created_at < NOW() - INTERVAL '2 years';
DELETE FROM session_logs
WHERE created_at < NOW() - INTERVAL '90 days';
DELETE FROM password_reset_tokens
WHERE created_at < NOW() - INTERVAL '24 hours';
5. Kryptering
GDPR kræver “passende tekniske foranstaltninger” til beskyttelse af persondata. Kryptering er den vigtigste.
At rest
- Krypter din databasedisk. Alle store cloud-udbydere understøtter dette. Aktiver det og verificer.
- For meget følsomme felter (CPR-numre, sundhedsdata) tilføj kryptering på applikationsniveau oveni.
- Krypter backups. En ukrypteret backup er et brud, der venter på at ske.
In transit
- TLS overalt. Hver forbindelse mellem tjenester, databaser og brugere. Ingen undtagelser.
- Gennemtving HTTPS. Omdiriger HTTP. Sæt HSTS-headers.
- Brug TLS til databaseforbindelser. PostgreSQL understøtter dette nativt.
// PostgreSQL connection with TLS
import { Pool } from "pg";
const pool = new Pool({
host: process.env.DB_HOST,
port: 5432,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: {
rejectUnauthorized: true,
ca: fs.readFileSync("/path/to/server-ca.pem").toString(),
},
});
6. Brudnotifikation
Hvis persondata kompromitteres, skal du underrette den relevante Datatilsynsmyndighed inden for 72 timer. Hvis bruddet udgør en høj risiko for enkeltpersoner, skal du også underrette de berørte brugere.
Hvad du skal bygge
- Audit-logning. Spor hver adgang til persondata. Hvem tilgik det, hvornår og hvorfra.
- Anomalidetektion. Alarmér ved usædvanlige adgangsmønstre (massedata-eksporter, adgang fra nye IP’er, adgang uden for arbejdstid).
- En hændelsesresponsplan. Dokumenter hvem der gør hvad, når et brud opdages. Det er ikke kode. Det er en tjekliste, dit team øver sig på.
CREATE TABLE data_access_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
accessed_by UUID NOT NULL,
access_type VARCHAR(50) NOT NULL,
resource_type VARCHAR(100) NOT NULL,
resource_id UUID,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_data_access_log_user
ON data_access_log (user_id, created_at);
CREATE INDEX idx_data_access_log_accessor
ON data_access_log (accessed_by, created_at);
7. Privacy by Design
GDPR siger, at privatliv skal bygges ind i systemer fra starten, ikke boltes på bagefter. I praksis betyder det at gøre privatliv til standarden.
- Standard er privat. Nye funktioner bør indsamle minimale data og kræve opt-in for alt ud over kernefunktionen.
- Indstillinger standardindstilles til den mest private mulighed. Brugere, der aldrig rører deres indstillinger, bør have den højeste privatlivsbeskyttelse.
- Adskil anliggender. Bland ikke analysedata med funktionelle data. Genbrug ikke auth-tokens til tracking.
Anonymisering vs. pseudonymisering
Disse er ikke det samme, og forskellen er vigtig.
- Pseudonymisering erstatter identificerende oplysninger med et reversibelt token. Eksempel: hashing af e-mailadresser. Hvis du har hash-funktionen og den originale e-mail, kan du genidentificere personen. GDPR gælder stadig, fordi genidentifikation er mulig.
- Anonymisering fjerner identificerende oplysninger permanent. Eksempel: aggregerede analyser (“1.247 brugere besøgte prissiden”) uden mulighed for at identificere, hvilke brugere. GDPR gælder ikke for ægte anonymiserede data.
Ægte anonymisering er svært. Hvis dit “anonyme” datasæt inkluderer et tidsstempel, en by og en enhedstype, kan den kombination unikt identificere nogen. Vær konservativ.
Cookie-samtykke
Hvis din hjemmeside bruger cookies ud over det strengt nødvendige, har du brug for samtykke, inden du sætter dem.
Kræver samtykke: analysecookies, reklamepixels, sociale medier-widgets, ethvert tredjeparts-trackingscript.
Kræver ikke samtykke: sessionscookies, indkøbskurvcookies, CSRF-tokens, selve cookie-samtykke-præferencecookien.
Dit samtykkebanner bør blokere ikke-essentielle cookies, indtil samtykke er givet, tilbyde granulære valg og gøre “afvis alle” lige så let som “accepter alle.” Byg det ikke fra bunden. Værktøjer som Cookiebot håndterer kompleksiteten. Hovedreglen: ingen tracking-scripts fyrer, før samtykke er givet.
Tredjeparts databehandlere
Hver tredjepartstjeneste, der håndterer dine brugeres data, er en “databehandler” under GDPR. Du er ansvarlig for deres compliance.
Hvad du skal tjekke
Før du integrerer nogen tredjepartstjeneste, der berører persondata:
- Har de en DPA? En Databehandleraftale er obligatorisk. De fleste SaaS-udbydere offentliggør deres.
- Hvor opbevarer de data? Hvis uden for EU, verificer det juridiske grundlag for overførslen.
- Hvilke data tilgår de? Minimer hvad du sender. Hvis tjenesten kun har brug for en e-mail, send ikke den fulde profil.
- Kan du slette data fra deres systemer? Brugers sletningsanmodninger skal propagere overalt.
- Hvordan håndterer de brud? Deres DPA bør specificere notifikationstidsrammer.
Almindelige tredjepartsbehandlere at gennemgå
- E-mail-tjenester (Resend, SendGrid, Mailchimp)
- Analyse (Google Analytics, Mixpanel, Amplitude)
- Fejlsporing (Sentry, Bugsnag)
- Betalingsbehandling (Stripe, Adyen)
- Cloud hosting (AWS, Google Cloud, Vercel)
- Kundesupportværktøjer (Intercom, Zendesk)
- AI API’er (OpenAI, Anthropic, Google AI)
Oprethold en liste over alle behandlere. Gennemgå den kvartalsvis.
Dataopbevaringspolitikker
Behold ikke persondata længere end nødvendigt. Definer opbevaringsperioder for hver datatype.
| Datatype | Foreslået opbevaring | Årsag |
|---|---|---|
| Brugerkontodata | Indtil sletning anmodes | Nødvendig for tjenesten |
| Sessionslogfiler | 90 dage | Sikkerhed og fejlfinding |
| Brugeraktivitetslogfiler | 1-2 år | Produktanalyse |
| Supportanmodninger | 3 år | Servicekvalitet |
| Finansielle poster | 7 år | Skat/juridiske forpligtelser |
| Nulstilling af adgangskode-tokens | 24 timer | Sikkerhed |
| Fejlslagne loginforsøg | 90 dage | Sikkerhedsovervågning |
Implementer automatiserede oprydningsjobs. Stol ikke på, at nogen husker at køre et script.
GDPR-tjekliste for udviklere
Brug denne som udgangspunkt, når du bygger eller reviderer et system.
Dataindsamling
- Hvert formularfelt har et angivet formål
- Ingen unødvendige data indsamles
- Privatlivspolitikken er linket fra hvert dataindsamlingspunkt
- Samtykke indsamles inden behandling (hvor påkrævet)
- Samtykkeposter gemmes med tidsstempler
Datalagring
- Databasekryptering at rest er aktiveret
- TLS er gennemtvunget for alle forbindelser
- Følsomme felter har kryptering på applikationsniveau
- Backups er krypterede
- Adgang til produktionsdata er begrænset og logget
Brugerrettigheder
- Dataeksport-endpoint eksisterer og dækker alle tabeller
- Kontosletnings-endpoint eksisterer og håndterer alle data
- Brugere kan se og tilbagekalde samtykke i deres indstillinger
- Sletning propagerer til tredjepartstjenester
- Alle anmodninger om brugerrettigheder besvares inden for 30 dage
Cookies og tracking
- Cookie-samtykkebanner er implementeret
- Ikke-essentielle cookies blokeres inden samtykke
- Samtykkevalg er granulære (ikke alt-eller-intet)
- “Afvis alle” er lige så fremtrædende som “Accepter alle”
Tredjeparter
- Alle databehandlere er dokumenterede
- DPA’er er underskrevet med hver behandler
- Data sendt til tredjeparter er minimeret
- Datasletning fra tredjeparter er mulig
Sikkerhed
- Audit-logs sporer adgang til persondata
- Anomalialarmering er konfigureret
- Hændelsesresponsplan er dokumenteret
- Brudnotifikationsproces er defineret (72-timers frist)
Opbevaring
- Opbevaringsperioder er defineret for alle datatyper
- Automatiserede oprydningsjobs er planlagt
- Udløbet data bliver faktisk slettet (verificer dette)
Afsluttende tanker
GDPR-compliance er ikke et engangsprojekt. Det er et sæt praksisser, der er vævet ind i, hvordan du bygger software. Det tekniske arbejde er ligetil: samtykkelagring, dataeksport, sletningsendpoints, kryptering, audit-logning. Standard ingeniørarbejde.
Den svære del er at være grundig. Det er nemt at glemme den logfil, den analysehændelse eller den tredjepartsintegration, der gemmer brugeres e-mails. Gennemgå regelmæssigt. Test dit sletningsendpoint. Verificer at dine eksporter er fuldstændige. Byg privatliv ind i din proces fra starten.
Har du brug for hjælp til at bygge GDPR-kompatibel software eller revidere dine eksisterende systemer? Kontakt os. Vi bygger privacy-first applikationer til europæiske virksomheder og virksomheder, der betjener EU-brugere.