# CORS-fejl i browseren

> Find den rigtige årsag bag en CORS-fejl mod api.danadresse.dk, som er åbent for alle domæner, og ret credentials, mixed content, proxyer og nøgletype.
>
> https://danadresse.dk/guides/cors-fejl · verified 2026-11-02
API'et tillader kald fra alle domæner. Får du alligevel en CORS-fejl i browserkonsollen, er årsagen næsten altid en anden end en manglende CORS-tilladelse: et kald med cookies, en `http://`-adresse, et kald, der aldrig nåede frem til API'et (et netværksproblem, en firewall, en browserudvidelse), eller en proxy, der ændrer headerne. Denne guide går årsagerne igennem i den rækkefølge, de typisk opstår.

## Hvad API'et sender

Alle svar fra API'et har `Access-Control-Allow-Origin: *`, også fejlene: 400, 401, 403 (også fra misbrugsfilteret), 404, 429, 500 og 503. Svar på et kald med en `Origin`-header lister også de headere, din kode må læse:

| Header | Værdi |
|---|---|
| `Access-Control-Allow-Origin` | `*` |
| `Access-Control-Expose-Headers` | `X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-RateLimit-Minute-Limit, X-RateLimit-Minute-Remaining, X-RateLimit-Minute-Reset, Retry-After, X-Quota-Grace, X-Quota-Remaining, ETag` |

Et preflight (`OPTIONS`) besvares med `Access-Control-Allow-Methods: GET, POST, OPTIONS`, de headere du beder om (fx `x-api-key` og `content-type`), og `Access-Control-Max-Age: 86400`. Preflight kræver ingen nøgle og tæller ikke som et kald.

Se det selv. Det første kald er et almindeligt GET med en `Origin`-header, det andet et preflight:

```bash id=cors-headers run=live expect="access-control-allow-origin"
curl -s -D - -o /dev/null "https://api.danadresse.dk/postnumre/8000" \
  -H "Origin: https://shop.example.dk" \
  -H "X-Api-Key: $DANADRESSE_API_KEY" | tr 'A-Z' 'a-z' | grep '^access-control'

curl -s -D - -o /dev/null -X OPTIONS "https://api.danadresse.dk/postnumre/8000" \
  -H "Origin: https://shop.example.dk" \
  -H "Access-Control-Request-Method: GET" \
  -H "Access-Control-Request-Headers: x-api-key" | tr 'A-Z' 'a-z' | grep '^access-control'
```

## Start i netværksfanen

Åbn udviklerværktøjernes netværksfane og find kaldet:

- **En status som 400, 401, 403, 404, 429 eller 500 er synlig.** Så er det ikke et CORS-problem. Klik på kaldet og læs svaret. Se [Fejlformatet](/guides/fejlformat).
- **Status 410 fra `dawa.aws.dk` eller `api.dataforsyningen.dk`.** Koden kalder stadig DAWA, som lukkede 1. oktober 2026. Søg hele kodebasen igennem, også bundlede filer og CMS-indstillinger, og skift til `https://api.danadresse.dk`. Se [Skift fra DAWA](/guides/migrer-fra-dawa).
- **Kaldet står som `(failed)`, `CORS error` eller `NS_ERROR_…` uden status.** Så nåede svaret ikke frem med CORS-headere. Gå tjeklisten igennem.

## Tjeklisten

**1. Kaldet sender cookies (credentials).**

- Symptom: Konsollen skriver noget i retning af `The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'`.
- Årsag: `fetch(url, { credentials: "include" })`, `axios` med `withCredentials: true` eller jQuery med `xhrFields: { withCredentials: true }`. Browsere afviser `*` sammen med credentials, og API'et bruger ikke cookies.
- Løsning: Fjern credentials fra kaldet. Nøglen skal i `X-Api-Key` eller `api_key`.

```js id=no-credentials run=no
const API_KEY = "DIN_BROWSER_NØGLE"; // låst til dit domæne

// Forkert: credentials: "include" giver en CORS-fejl
// Rigtigt:
const res = await fetch("https://api.danadresse.dk/autocomplete?q=Vesterbrogade", {
  headers: { "X-Api-Key": API_KEY },
});
```

**2. Mixed content: `http://` fra en `https://`-side.**

- Symptom: Konsollen skriver `Mixed Content`, eller netværksfanen viser `blocked:mixed-content`.
- Årsag: API-adressen i koden starter med `http://`. Browseren blokerer kaldet, før det bliver sendt.
- Løsning: Brug `https://api.danadresse.dk`.

**3. Kaldet nåede aldrig frem til API'et.**

- Årsag: Alle svar fra selve API'et har CORS-headere, også fejlene. En CORS-fejl uden status betyder derfor som regel, at svaret kom et andet sted fra, eller slet ikke kom:
  - en annonceblokker eller privatlivsudvidelse i browseren, der blokerer `api.danadresse.dk`
  - en firmafirewall, proxy eller et VPN, der afbryder eller ændrer kaldet
  - en 502 eller 504 fra proxyen foran API'et, fx ved en timeout eller under en udrulning
  - en tabt forbindelse (offline, DNS)
- Løsning: Prøv et privat vindue uden udvidelser, og kør derefter det samme kald med curl fra samme netværk. Får curl et svar med `access-control-allow-origin`, ligger problemet i browseren eller netværket. Får du 502 eller 504, så se [Fejl 503 og timeouts](/guides/fejl-503-og-timeouts).

**4. Din egen proxy ændrer headerne.**

- Symptom: Konsollen skriver `The 'Access-Control-Allow-Origin' header contains multiple values '*, *'`.
- Årsag: Du kalder API'et gennem din egen server (nginx, en Next.js-route, en Cloudflare Worker), og proxyen både sender vores `Access-Control-Allow-Origin` videre og tilføjer sin egen.
- Løsning: Lad kun ét led sætte CORS-headere. Kalder din side din egen proxy på samme domæne, behøver du slet ingen CORS-headere. Se [Next.js route handler som proxy](/guides/nextjs-proxy).

```js id=proxy-strip-headers run=no
// Next.js route handler: send kun body og content-type videre
const upstream = await fetch(url, { headers: { "X-Api-Key": process.env.DANADRESSE_API_KEY } });
return new Response(upstream.body, {
  status: upstream.status,
  headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" },
});
```

**5. Gammel JSONP-kode.**

- Sådan virker det: `$.ajax({ dataType: "jsonp" })` og `<script>`-tags med `callback=` virker på DAWA-stierne. Svaret pakkes ind som `fn(…);` med `Content-Type: application/javascript`, og JSONP kræver slet ikke CORS. Et script-tag kan ikke sende headere, så nøglen skal i URL'en som `api_key=`.
- Fejler det alligevel: callback-navnet skal være et almindeligt JavaScript-navn som `cb` eller `jQuery123_456` (ellers får du almindelig JSON tilbage), `/api/v1` svarer ikke med JSONP, og en Content Security Policy på din side skal tillade `https://api.danadresse.dk` i `script-src`.
- Løsning: Brug `fetch` i ny kode. Se [Formater](/guides/formater-json-csv-geojson).

## Ting, der ligner CORS-fejl, men ikke er det

**En header er `null` i JavaScript.**
Browseren lader koden læse headerne i `Access-Control-Expose-Headers` (se tabellen ovenfor) og de standardheadere, der altid er tilladt. Så `res.headers.get("Retry-After")` og headerne `X-RateLimit-*` og `X-Quota-*` virker i browseren. Andre headere, fx `X-Danadresse-Key-Status`, bliver sendt, men giver `null` i browserkode. Læs dem på serveren.

**403 origin_not_allowed.**
Nøglen er låst til domæner, og siden står ikke på listen. Svaret har CORS-headere og en læsbar body. Se [Fejl 403](/guides/fejl-403).

**Servernøglen virker i browseren.**
En nøgle uden domænelås virker fra enhver side, fordi CORS er åbent. Det er ikke en sikkerhedsgrænse: enhver kan kopiere nøglen fra sidens kildekode. Brug en browsernøgle med *Tilladte domæner* i frontend-kode, og hold servernøglen på serveren. Se [Browsernøgler og tilladte domæner](/guides/browser-noegler-domaener).

## Næste skridt

- [Browsernøgler og tilladte domæner](/guides/browser-noegler-domaener)
- [Next.js route handler som proxy](/guides/nextjs-proxy)
- [Fejl 403: adgang nægtet](/guides/fejl-403)
