By the end of the lesson, you can say when two URLs have the same origin, what the same-origin policy restricts and what it allows, how CORS and the preflight work, and why CORS is not access protection.
Why do pages need to be isolated?
The student opens Kurzuspont and a foreign web page at the same time. If the JavaScript of the foreign page could request anything in Kurzuspont's name and read the response, the logged-in student's personal data would fall into the attacker's hands: the browser would attach the cookie too. The browser therefore maintains a rule: pages of different origins cannot read each other's data by default. This is the same-origin policy.
What does origin mean?
The origin is the combination of scheme, host name, and port. According to MDN's example, relative to the address http://store.company.com/dir/page.html:
Other URL
Same origin?
Why?
http://store.company.com/dir2/other.html
yes
only the path differs
https://store.company.com/page.html
no
different scheme
http://store.company.com:81/dir/page.html
no
different port (the default port of http is 80)
http://news.company.com/dir/page.html
no
different host name
The path is therefore not part of the origin. (According to MDN, cookies follow a different, domain- and path-based interpretation, not the origin.) The contents of localStorage and IndexedDB are likewise separated by origin (3.6).
flowchart LR
A[page of kurzuspont.example] -->|same origin| P[API of kurzuspont.example]
A -.->|cross-origin: CORS needed for reading| Q[api.kurzuspont.example]
X[page of idegen.example] -.->|reading restricted| P
What does it restrict and what does it allow?
According to MDN's summary:
Forbidden (by default): the script of one page cannot read the response of another origin (a fetch() response), and cannot access the stored data of another origin.
Allowed:cross-origin writes (links, redirects, form submissions) and embedding (<script src>, <img>, <iframe>, styles), during which the browser sends a request, but the script cannot read the result.
This is a very important distinction: the same-origin policy does not say that no request can start from the browser toward another origin. It says that the JavaScript of the sending page generally cannot read the response. The server can still receive the request, and a side effect can even occur (8.4).
What does CORS do?
CORS (Cross-Origin Resource Sharing) is a mechanism working with HTTP headers by which the server can say which other origins may load its resources or read its response in the browser. How it works:
The browser sends an Origin header in the cross-origin request.
The server names the permitted origin in the Access-Control-Allow-Origin response header.
If the response allows the requester's origin, the browser hands the response to the script; if not, it blocks it.
Tried with a trial server that allows only the origin https://kurzuspont.example:
To the second request the server responded just the same (200, with a body), only the Access-Control-Allow-Origin header is missing. The browser would therefore not hand the response to the script of the foreign page. curl, however, is not a browser: it does not enforce CORS, so it sees the same body. CORS is thus a browser-side reading restriction, not access protection.
Vary: Origin tells caches that the response depends on the value of Origin (MDN recommends it for a dynamic origin).
Preflight
According to MDN, a simple request is one that has the method GET, HEAD, or POST, uses only headers classed as safe, and whose Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain. For the others (for example Content-Type: application/json or a custom header) the browser first sends a preflightOPTIONS request, and continues with the actual request only on the basis of the permissions.
sequenceDiagram
participant B as Browser
participant A as API
B->>A: OPTIONS + Origin + planned method and header
A-->>B: Permitted origin, method, headers
B->>A: Actual API request
A-->>B: Response + CORS headers
Access-Control-Max-Age says how long the result of the preflight can be stored (according to MDN the default is 5 seconds). If for a foreign origin the preflight gives no permission (the server above then gives an empty 204 response, without Access-Control-Allow-*), the browser does not even send the actual request.
Cookies and the asterisk
By default, cross-origin requests do not send credentials (cookies). If it is nevertheless necessary, the client must ask for it (credentials: "include"), and the server must answer with Access-Control-Allow-Credentials: true. According to MDN, in that case the wildcard value * cannot be used in the Access-Control-Allow-Origin header: an explicit origin must be given. The cookies' own SameSite rules form a separate layer from this (7.2).
What is CORS not for?
A common mistake is that developers "solve" a CORS error by setting the origin to *. This only lifts the browser's reading restriction for everyone; if the response contains personal data, the access protection must not be given by CORS.
Worked example: the separate API domain of Kurzuspont
The interface of Kurzuspont runs at https://kurzuspont.example, the API at https://api.kurzuspont.example. The two addresses have different host names, so they are different origins.
Situation
What happens?
The interface asks the API for JSON
A preflight (JSON Content-Type), to which the API permits the origin https://kurzuspont.example.
A foreign page would ask the same
It gets no permission to the preflight, the browser does not send the actual request.
The interface would also send a cookie
credentials: "include", the API gives an exact origin and an Allow-Credentials: true header.
A script calls the API with curl
CORS does not apply; the API must defend itself with authentication and authorization.
Common misconceptions
Statement
Clarification
"CORS protects the API from unwanted clients."
In the browser it regulates the reading of the response; server-side access protection is needed too.
"Another path is already another origin."
The path is not part of the origin.
"A CORS error means the request did not go out."
For a simple request the request may go out, only the response is not readable.
"Every cross-origin request is preceded by OPTIONS."
Only non-simple requests.
"The * is always a good solution."
It cannot be used for a request with cookies, and is dangerous for personal data.
In brief
The origin = scheme + host name + port; the path is not part of it.
The same-origin policy restricts cross-origin reading, and allows writing and embedding.
CORS permits cross-origin reading with the server's Access-Control-* headers; before a non-simple request there is a preflight.
CORS is a browser mechanism: it has no effect on curl, and does not replace an authorization check.
Review questions
Do https://kurzuspont.example and https://kurzuspont.example:8443 have the same origin?
What does the same-origin policy allow and what does it forbid?
When does the browser send a preflight request?
Why does CORS not protect the API against a directly run script?
Same-origin policy and CORS
Objectives
By the end of the lesson, you can say when two URLs have the same origin, what the same-origin policy restricts and what it allows, how CORS and the preflight work, and why CORS is not access protection.
Why do pages need to be isolated?
The student opens Kurzuspont and a foreign web page at the same time. If the JavaScript of the foreign page could request anything in Kurzuspont's name and read the response, the logged-in student's personal data would fall into the attacker's hands: the browser would attach the cookie too. The browser therefore maintains a rule: pages of different origins cannot read each other's data by default. This is the same-origin policy.
What does origin mean?
The origin is the combination of scheme, host name, and port. According to MDN's example, relative to the address
http://store.company.com/dir/page.html:http://store.company.com/dir2/other.htmlhttps://store.company.com/page.htmlhttp://store.company.com:81/dir/page.htmlhttpis 80)http://news.company.com/dir/page.htmlThe path is therefore not part of the origin. (According to MDN, cookies follow a different, domain- and path-based interpretation, not the origin.) The contents of
localStorageand IndexedDB are likewise separated by origin (3.6).flowchart LR A[page of kurzuspont.example] -->|same origin| P[API of kurzuspont.example] A -.->|cross-origin: CORS needed for reading| Q[api.kurzuspont.example] X[page of idegen.example] -.->|reading restricted| PWhat does it restrict and what does it allow?
According to MDN's summary:
fetch()response), and cannot access the stored data of another origin.<script src>,<img>,<iframe>, styles), during which the browser sends a request, but the script cannot read the result.This is a very important distinction: the same-origin policy does not say that no request can start from the browser toward another origin. It says that the JavaScript of the sending page generally cannot read the response. The server can still receive the request, and a side effect can even occur (8.4).
What does CORS do?
CORS (Cross-Origin Resource Sharing) is a mechanism working with HTTP headers by which the server can say which other origins may load its resources or read its response in the browser. How it works:
Originheader in the cross-origin request.Access-Control-Allow-Originresponse header.Tried with a trial server that allows only the origin
https://kurzuspont.example:To the second request the server responded just the same (200, with a body), only the
Access-Control-Allow-Originheader is missing. The browser would therefore not hand the response to the script of the foreign page.curl, however, is not a browser: it does not enforce CORS, so it sees the same body. CORS is thus a browser-side reading restriction, not access protection.Vary: Origintells caches that the response depends on the value ofOrigin(MDN recommends it for a dynamic origin).Preflight
According to MDN, a simple request is one that has the method
GET,HEAD, orPOST, uses only headers classed as safe, and whoseContent-Typeisapplication/x-www-form-urlencoded,multipart/form-data, ortext/plain. For the others (for exampleContent-Type: application/jsonor a custom header) the browser first sends a preflightOPTIONSrequest, and continues with the actual request only on the basis of the permissions.sequenceDiagram participant B as Browser participant A as API B->>A: OPTIONS + Origin + planned method and header A-->>B: Permitted origin, method, headers B->>A: Actual API request A-->>B: Response + CORS headersAccess-Control-Max-Agesays how long the result of the preflight can be stored (according to MDN the default is 5 seconds). If for a foreign origin the preflight gives no permission (the server above then gives an empty204response, withoutAccess-Control-Allow-*), the browser does not even send the actual request.Cookies and the asterisk
By default, cross-origin requests do not send credentials (cookies). If it is nevertheless necessary, the client must ask for it (
credentials: "include"), and the server must answer withAccess-Control-Allow-Credentials: true. According to MDN, in that case the wildcard value*cannot be used in theAccess-Control-Allow-Originheader: an explicit origin must be given. The cookies' ownSameSiterules form a separate layer from this (7.2).What is CORS not for?
A common mistake is that developers "solve" a CORS error by setting the origin to
*. This only lifts the browser's reading restriction for everyone; if the response contains personal data, the access protection must not be given by CORS.Worked example: the separate API domain of Kurzuspont
The interface of Kurzuspont runs at
https://kurzuspont.example, the API athttps://api.kurzuspont.example. The two addresses have different host names, so they are different origins.Content-Type), to which the API permits the originhttps://kurzuspont.example.credentials: "include", the API gives an exact origin and anAllow-Credentials: trueheader.curlCommon misconceptions
OPTIONS."*is always a good solution."In brief
Access-Control-*headers; before a non-simple request there is a preflight.curl, and does not replace an authorization check.Review questions
https://kurzuspont.exampleandhttps://kurzuspont.example:8443have the same origin?Learning resources