Iframe-embedding toestaan voor een partner

Aan het einde van deze gids kan een vertrouwde partner-origin de Tapster-frontend in een <iframe> laden, en is dat lokaal en in productie bevestigd.

Voorwaarden:

  • Je hebt schrijftoegang tot deze repo en kunt een PR mergen.
  • Je kent de exacte origin van de partner (scheme + host, bijvoorbeeld https://klant.nl of https://*.partner.nl). Geen pad, geen trailing slash.
  • De partner-origin gebruikt HTTPS. HTTP is niet toegestaan in productie.
  • Docker en npm zijn beschikbaar als je lokaal wilt testen.

Achtergrond

De frontend-nginx zet één security-header die iframe-embedding bepaalt:

add_header Content-Security-Policy "frame-ancestors 'self' <partner-origins>" always;

'self' betekent: alleen Tapster zelf mag zichzelf embedden. Elke origin die je achter 'self' zet, wordt extra toegestaan. Browsers blokkeren alle andere origins met een console-melding van het type “Refused to display … in a frame because an ancestor violates the Content Security Policy directive”.

X-Frame-Options staat bewust niet meer in de config: die kan geen allowlist met meerdere origins uitdrukken, en moderne browsers honoreren CSP frame-ancestors er toch boven.

1. Voeg de origin toe aan nginx.conf

Open apps/frontend/nginx.conf en pas de Content-Security-Policy-regel aan. Voorbeeld voor één partner:

- add_header Content-Security-Policy "frame-ancestors 'self'" always;
+ add_header Content-Security-Policy "frame-ancestors 'self' https://klant.nl" always;

Meerdere origins zijn spatie-gescheiden:

add_header Content-Security-Policy "frame-ancestors 'self' https://klant.nl https://*.partner.nl" always;

Regels:

  • Gebruik altijd https://.
  • Geen pad of query (https://klant.nl/portaal is ongeldig en wordt door browsers genegeerd).
  • Wildcard mag alleen op subdomein-niveau: https://*.partner.nl is goed, https://*.nl niet.
  • Schrijf elke origin uit (geen variabelen of placeholders), zodat de header bij elke nginx -s reload en in curl -I direct verifieerbaar is.

2. Test lokaal

Vanuit de repo-root:

docker build -f apps/frontend/Dockerfile -t tapster-frontend-csp-test .
docker run --rm -d --name csp-test -p 8080:80 \
  -e URL_API=http://localhost:3000 \
  -e URL_APIV6=http://localhost:3000 \
  tapster-frontend-csp-test

Controleer de header:

curl -sI http://localhost:8080/ | grep -i content-security-policy

Verwachte output (één regel, met jouw allowlist):

content-security-policy: frame-ancestors 'self' https://klant.nl

Maak optioneel een minimale embed-test:

cat > /tmp/embed-test.html <<'EOF'
<!doctype html><meta charset="utf-8"><title>embed-test</title>
<iframe src="http://localhost:8080/" style="width:100%;height:90vh;border:0"></iframe>
EOF
open /tmp/embed-test.html

Open de DevTools-console van die pagina. Geen CSP-foutmelding betekent dat embedding werkt voor die origin. (Voor de echte partner-origin moet je de src= invullen met de productie-frontend-URL en het testbestand serveren vanaf de partner-origin, anders test je alleen 'self'.)

Opruimen:

docker stop csp-test

3. PR aanmaken en mergen

Volg de standaard PR-flow (feat/-branch, conventional commit, automatische PR via CI). Vermeld in de PR-beschrijving:

  • Welke partner-origin is toegevoegd.
  • Op welke datum de partner is geverifieerd.
  • Een link naar de afspraak of ticket waarin de embedding is toegezegd.

4. Verifieer in productie

Na deploy:

curl -sI https://app.tapster.nl/ | grep -i content-security-policy

De allowlist moet exact bevatten wat je in stap 1 hebt toegevoegd. Vraag de partner kort te bevestigen dat hun pagina de Tapster-frontend toont zonder iframe-foutmelding in de browser-console.

Een origin weer intrekken

Verwijder de origin uit dezelfde regel in apps/frontend/nginx.conf, open een nieuwe PR, en herhaal stap 2 en 4. Communiceer de intrekking vooraf met de partner, anders breekt hun pagina zonder waarschuwing.

Veelvoorkomende fouten

  • Origin met pad of trailing slash. https://klant.nl/ of https://klant.nl/portaal werkt niet; gebruik https://klant.nl.
  • http:// in productie. Browsers staan mixed-content iframes niet toe; gebruik altijd HTTPS.
  • Wildcard op TLD-niveau. https://*.nl is ongeldig, gebruik https://*.klantgroep.nl.
  • Vergeten dat cookies een SameSite=None; Secure-attribuut nodig hebben als de partner-origin afwijkt van Tapster zelf en de gebruiker binnen de iframe ingelogd moet zijn. Check eerst of dat voor jouw use-case van toepassing is.