How it works
The explanation, with examples you can run.
Origins
An origin is the scheme, host and port of a URL. https://shop.example and https://shop.example/cart share one; change any of the three parts and it is a different origin:
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
A page may still embed cross-origin images, scripts and styles, and submit forms anywhere. What it can't do by default is read a cross-origin response from fetch, or reach into another origin's frame.
CORS
When a script fetches another origin, the browser sends an Origin header and only hands the response to the script if the reply carries a matching Access-Control-Allow-Origin. Requests that a plain HTML form couldn't make (methods like PUT or DELETE, a JSON Content-Type, custom headers) are preflighted: the browser first sends an OPTIONS request asking permission, and only sends the real one if the answer allows it. With cookies (credentials: "include"), the server must name the exact origin and send Access-Control-Allow-Credentials: true.
CORS is enforced by browsers, for browsers. curl ignores it, so it is never a substitute for authentication on your API.
XSS and CSP
Cross-site scripting is attacker-controlled text ending up in your page as code. Stored XSS comes from your database, reflected XSS from the URL, DOM-based XSS from your own client code (innerHTML, document.write, eval, javascript: URLs). Defences, in order:
-
Treat data as text. Use
textContent,setAttributeand your framework's default escaping; reach forinnerHTMLonly with sanitised HTML. -
Escape for the context you are writing into: HTML, an attribute, a URL or a script each need different handling.
-
Send a strict Content Security Policy, for example
script-src 'nonce-RANDOM' 'strict-dynamic'; object-src 'none'; base-uri 'none', with a fresh random nonce on each response. Injected scripts without the nonce don't run, and inline event handlers are blocked.frame-ancestorsin the same header controls who may frame your page.
For text inside HTML, escaping five characters is enough to keep it text:
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
Cookies
Session cookies should be HttpOnly (unreadable by scripts, so an XSS can't steal them), Secure (HTTPS only) and SameSite=Lax or Strict. SameSite governs cross-site requests, where a site is the registrable domain plus the scheme, so app.shop.example and api.shop.example are the same site but different origins. Some browsers treat a cookie without SameSite as Lax, others don't, so always set it explicitly. SameSite=None is needed for genuinely cross-site uses such as embedded widgets, and must be combined with Secure.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.