Browsernøgler og tilladte domæner

Lås en nøgle til dine domæner i dashboardet eller med PATCH /api/v1/keys/{id}, så den kan stå i en offentlig webside uden at blive misbrugt.

5 min Niveau: Begynder ✓ Kode testet

Et adressefelt, der kalder API'et direkte fra browseren, skal have nøglen med i sidens kildekode. Alle kan åbne udviklerværktøjerne og kopiere den. En browsernøgle er en almindelig nøgle med en liste over tilladte domæner. API'et svarer kun, når kaldet kommer fra en side på et af dem.

Uden låsen ville nøglen også blive fanget af lækagekontrollen: en live-nøgle, der bruges fra mange forskellige IP-adresser på én dag, bliver markeret som lækket og spærret efter en frist. En domænelåst nøgle er undtaget fra den kontrol, fordi det er forventet, at hver besøgende har sin egen IP.

1. Opret en nøgle til browseren

Opret en ny nøgle under Nøgler og giv den et navn, der siger, hvor den bruges, fx "webshop checkout". Brug aldrig den samme nøgle i browseren og på din server:

  • En browsernøgle virker kun, når kaldet har en Origin- eller Referer-header fra et tilladt domæne. Kald fra en server har ingen af dem og bliver afvist med 403.
  • En browsernøgle må kun lave adresseopslag (se trin 5).

Du kan også låse en testnøgle (dawa_test_…) til localhost og bruge den under udvikling.

2. Sæt domænerne i dashboardet

Under hver nøgle på Nøgler er der en sektion, der hedder Tilladte domæner. Skriv domænerne i feltet, adskilt af komma, mellemrum eller linjeskift, og klik Gem. Et tomt felt gør nøglen til en almindelig servernøgle igen.

Du kan indsætte hele URL'er. https://www.firma.dk/kassen bliver gemt som www.firma.dk. Port og protokol bliver fjernet.

Når låsen afviser kald, viser nøglen, hvilke domæner der er afvist de sidste 7 dage og hvor mange gange, fx shop.firma.dk (108×). Er det jeres egen side, så tilføj domænet til listen.

3. Eller sæt dem med API'et

Har du mange nøgler eller sites, kan du sætte listen med PATCH /api/v1/keys/{id}. Endpointet kræver en JWT, ikke en API-nøgle. Du får den fra POST /api/v1/auth/login med din e-mail og adgangskode. Logger du ind med Google eller kode på mail og har ingen adgangskode, så sæt en via Glemt adgangskode først.

Scriptet logger ind, viser dine nøgler og sætter domænerne på én af dem. Det kræver pip install requests og tre miljøvariabler:

Python#patch-origins
import os
import sys

import requests

BASE = "https://api.danadresse.dk"

login = requests.post(
    f"{BASE}/api/v1/auth/login",
    json={"email": os.environ["DANADRESSE_EMAIL"], "password": os.environ["DANADRESSE_PASSWORD"]},
    timeout=10,
)
login.raise_for_status()
auth = {"Authorization": f"Bearer {login.json()['access_token']}"}

# Vis nøglerne, så du kan finde id'et
for k in requests.get(f"{BASE}/api/v1/keys", headers=auth, timeout=10).json():
    print(k["id"], k["mode"], k["name"], k["allowed_origins"])

key_id = os.environ["DANADRESSE_KEY_ID"]
r = requests.patch(
    f"{BASE}/api/v1/keys/{key_id}",
    headers=auth,
    json={"allowed_origins": ["firma.dk", "www.firma.dk", "*.firma.dk", "localhost"]},
    timeout=10,
)
if r.status_code != 200:
    fejl = r.json().get("error", {})
    sys.exit(f"{r.status_code} {fejl.get('code')}: {fejl.get('message')}")
print("Tilladte domæner:", r.json()["allowed_origins"])

Svaret indeholder listen, som den blev gemt efter normalisering:

JSON#patch-response
{
  "id": "4f1c0b7e-2a9d-4c55-9a51-0d3c7f1e8b20",
  "allowed_origins": ["firma.dk", "www.firma.dk", "*.firma.dk", "localhost"]
}

{"allowed_origins": []} fjerner låsen. Et body uden allowed_ips ændrer ikke en eventuel IP-liste på nøglen. Ugyldige domæner giver 422 med koden invalid_origins og en dansk fejltekst. En nøgle, der ikke findes, er tilbagekaldt eller tilhører en anden konto, giver 404.

4. Sådan matches domænerne

API'et tager værtsnavnet fra Origin-headeren. Mangler den, bruges Referer. Protokol og port tæller ikke med.

MønsterMatcherMatcher ikke
firma.dkfirma.dkwww.firma.dk, shop.firma.dk
www.firma.dkwww.firma.dkfirma.dk
*.firma.dkwww.firma.dk, shop.firma.dk, a.b.firma.dkfirma.dk
localhostlocalhost på alle porte127.0.0.1
127.0.0.1127.0.0.1 på alle portelocalhost

Regler for listen:

  • Et wildcard må kun stå forrest, som *.firma.dk. *, www.* og *.*.firma.dk afvises.
  • *.firma.dk dækker kun underdomæner. Kører dit site både på firma.dk og www.firma.dk, skal du skrive begge eller firma.dk og *.firma.dk.
  • Højst 40 domæner pr. nøgle og højst 700 tegn i alt. Brug et wildcard i stedet for mange underdomæner.
  • localhost er tilladt til udvikling. Åbner du en HTML-fil direkte fra disken (file://), sender browseren Origin: null, og kaldet bliver afvist. Start en lokal server i stedet, fx npx serve eller python3 -m http.server.

5. Hvad en browsernøgle ikke må

En domænelåst nøgle dækker adresseopslag: /autocomplete, /adresser, /adgangsadresser, /datavask/adresser, /postnumre, /kommuner og de øvrige DAWA-kompatible endpoints. Den bliver afvist med 403 og koden browser_key_scope på:

  • /api/v1/enrich/… (BBR, DAGI, ejendom, klima osv.)
  • /api/v1/datavask/parse
  • /api/v1/stats/national

De kalder videre til offentlige registre og skal kaldes fra din server med en servernøgle. Har du brug for dem på en webside, så lav en proxy som i Next.js route handler som proxy.

6. Grænser for en browsernøgle

Kald med en browsernøgle tæller i kontoens månedlige kvote og minutgrænse, ligesom alle andre kald. Derudover har hver besøgendes IP-adresse sin egen grænse på 120 kald i minuttet pr. nøgle. Overskrides den, svarer API'et 429 med koden rate_limit_per_visitor og en Retry-After-header med antal sekunder, til minutvinduet starter forfra (1-60). På den måde kan én besøgende ikke bruge hele din kvote. Afviste kald tæller ikke i din kvote.

Svar fra API'et, også fejl, har CORS-headere og gør Retry-After, X-RateLimit-*, X-RateLimit-Minute-* og X-Quota-* læsbare for JavaScript. Efter en 429 læser du res.headers.get("Retry-After") og venter så længe før næste kald. Med debounce på 150-200 ms i dit adressefelt kommer en almindelig bruger ikke i nærheden af grænsen.

7. Test låsen

Fra terminalen kan du sætte Origin-headeren selv og se, om låsen opfører sig, som du forventer. Byt nøglen ud med din browsernøgle:

Shell#test-origin
# Din browser-nøgle
export BROWSER_KEY="…"

# Tilladt domæne → 200 og en liste af forslag
curl -s -o /dev/null -w "%{http_code}\n" \
  -H "X-Api-Key: $BROWSER_KEY" -H "Origin: https://www.firma.dk" \
  "https://api.danadresse.dk/autocomplete?q=Strandvejen"

# Andet domæne → 403 origin_not_allowed
curl -s -H "X-Api-Key: $BROWSER_KEY" -H "Origin: https://andet.dk" \
  "https://api.danadresse.dk/autocomplete?q=Strandvejen"

Det andet kald giver et svar som dette. Teksten fortæller, hvilke domæner nøglen er låst til, og hvilket domæne kaldet kom fra:

JSON#origin-not-allowed
{
  "type": "ForbiddenError",
  "title": "This API key is locked to firma.dk, www.firma.dk and this request came from andet.dk. Add the domain under API-nøgler → Tilladte domæner, or use a server-side key.",
  "details": null,
  "code": "origin_not_allowed"
}

Hvor meget beskytter låsen?

Origin og Referer sættes af browseren, og et webscript på en anden side kan ikke ændre dem. Uden for en browser kan alle sætte de headers, de vil, som curl-eksemplet viser. Låsen forhindrer altså, at andre kopierer din nøgle ind på deres egen hjemmeside. Den forhindrer ikke et script på en server i at efterligne dit domæne. Grænsen pr. besøgende og kontoens kvote er bagstopperen.

Tror du, at nøglen bliver misbrugt, så opret en ny, sæt de samme domæner på, skift den ud på siden og tilbagekald den gamle. Se Roter nøgler.

Næste skridt

Relaterede guides

Markdown-kilde Fejl i guiden? Skriv til os API-reference (Swagger)

Prøv det med en gratis nøgle

2 000 kald/måned på gratisplanen. Intet kreditkort.

Opret konto →