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.
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- ellerReferer-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:
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:
{
"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ønster | Matcher | Matcher ikke |
|---|---|---|
firma.dk | firma.dk | www.firma.dk, shop.firma.dk |
www.firma.dk | www.firma.dk | firma.dk |
*.firma.dk | www.firma.dk, shop.firma.dk, a.b.firma.dk | firma.dk |
localhost | localhost på alle porte | 127.0.0.1 |
127.0.0.1 | 127.0.0.1 på alle porte | localhost |
Regler for listen:
- Et wildcard må kun stå forrest, som
*.firma.dk.*,www.*og*.*.firma.dkafvises. *.firma.dkdækker kun underdomæner. Kører dit site både påfirma.dkogwww.firma.dk, skal du skrive begge ellerfirma.dkog*.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.
localhoster tilladt til udvikling. Åbner du en HTML-fil direkte fra disken (file://), sender browserenOrigin: null, og kaldet bliver afvist. Start en lokal server i stedet, fxnpx serveellerpython3 -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:
# 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:
{
"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
- Autocomplete-widget i ren JavaScript
- Next.js route handler som proxy — når nøglen slet ikke skal i browseren.
- Fejl 403 — alle 403-koder og hvad de betyder.
Relaterede guides
Markdown-kilde Fejl i guiden? Skriv til os API-reference (Swagger)