Пошук уроків, статей та іншого контенту
З’ясуєте, як Same-Origin Policy ізолює документи, скрипти та ресурси між різними джерелами.
У браузері джерело (origin) — це комбінація трьох частин URL:
Схема — наприклад, http або https.
Хост — доменне ім’я або IP-адреса.
Порт — наприклад, 80, 443 або 3000.
Два документи мають однакове джерело лише тоді, коли всі три частини збігаються.
Наприклад:
https://example.com:443/page.html
https://example.com:443/script.jsЦі адреси мають однакове джерело:
https://example.com:443Шлях (/page.html або /script.js) не впливає на джерело.
https://example.com
http://example.com
https://api.example.com
https://example.com:3000Ці адреси мають різні джерела, тому що відрізняється:
схема: http і https;
хост: example.com і api.example.com;
порт: стандартний порт HTTPS і 3000.
Навіть два порти на одному комп’ютері є різними джерелами:
http://localhost:3000
http://localhost:5173Порівняння:
https://example.com/page-a
https://example.com/page-bЦе одне джерело, тому що схема, хост і порт однакові.
Властивість location.origin повертає джерело поточного документа.
Цей приклад можна зберегти у файл origin.html і відкрити в браузері:
<!doctype html>
<html lang="uk">
<head>
<meta charset="utf-8">
<title>Перевірка джерел</title>
</head>
<body>
<h1>Перевірка джерел</h1>
<p id="current-origin"></p>
<pre id="result"></pre>
<script>
const currentOrigin = location.origin;
document.querySelector("#current-origin").textContent =
`Джерело цього документа: ${currentOrigin}`;
const addresses = [
`${currentOrigin}/page.html`,
"https://example.com/page.html",
"http://example.com/page.html",
"https://api.example.com/data",
"http://localhost:3000/api"
];
const lines = addresses.map((address) => {
const url = new URL(address);
return [
`URL: ${url.href}`,
`Джерело: ${url.origin}`,
`Збігається з поточним: ${url.origin === currentOrigin}`
].join("\n");
});
document.querySelector("#result").textContent = lines.join("\n\n");
</script>
</body>
</html>Якщо відкрити файл без вебсерверу, браузер може показати джерело null, оскільки локальні файли мають особливе непрозоре джерело. Для реальних експериментів краще запускати сторінку через локальний вебсервер.
Same-Origin Policy, або політика того самого джерела, — це правило безпеки браузера.
Воно забороняє документу або скрипту з одного джерела довільно читати захищені дані з іншого джерела.
Наприклад, сторінка:
https://shop.exampleне може без додаткового дозволу прочитати відповідь API:
https://bank.example/accountЦе важливо, тому що користувач може бути авторизований на банківському сайті. Без Same-Origin Policy шкідливий сайт міг би відкрити банківську сторінку, прочитати баланс або історію операцій і передати ці дані зловмиснику.
Політика ізолює:
документи;
DOM-дерево сторінок;
відповіді мережевих запитів;
доступ до даних у фреймах;
деякі можливості роботи з ресурсами.
Сторінка може вбудувати іншу сторінку через iframe:
<iframe src="https://example.com" id="external-page"></iframe>
<script>
const frame = document.querySelector("#external-page");
try {
// Такий доступ буде заборонено, якщо джерела різні.
console.log(frame.contentDocument.body.textContent);
} catch (error) {
console.error("Доступ до DOM заборонено:", error);
}
</script>Якщо поточна сторінка та iframe мають різні джерела, браузер не дозволить прочитати DOM фрейму.
Важливо, що це обмеження не означає, що iframe неможливо показати. Сторінку часто можна вбудувати, але не можна довільно прочитати або змінити її вміст.
Якщо документи мають однакове джерело, доступ можливий:
https://example.com/index.html
https://example.com/profile.htmlУ такому випадку скрипт може взаємодіяти з DOM іншого документа в iframe, якщо додаткові обмеження не забороняють це.
Same-Origin Policy не забороняє абсолютно всі дії між різними джерелами.
Браузер часто дозволяє завантажувати або вбудовувати ресурси:
<img src="https://cdn.example/image.png" alt="Зображення">
<script src="https://cdn.example/library.js"></script>
<link rel="stylesheet" href="https://cdn.example/styles.css">
<iframe src="https://example.com"></iframe>Але можливість вбудувати ресурс не означає можливість прочитати його вміст у JavaScript.
Наприклад, зображення можна показати, але спроба прочитати пікселі чужого зображення через canvas може бути заблокована. Це запобігає крадіжці даних із зображень, до яких поточний сайт не має доступу.
Також браузер може дозволити перейти на іншу сторінку або надіслати HTML-форму, але це не означає, що JavaScript зможе прочитати відповідь.
Запити через fetch() та XMLHttpRequest підкоряються Same-Origin Policy.
Припустімо, сторінка працює за адресою:
https://shop.exampleВона може виконати запит до того самого джерела:
fetch("/api/products")
.then((response) => response.json())
.then((products) => {
console.log(products);
})
.catch((error) => {
console.error("Помилка запиту:", error);
});Повна адреса запиту буде:
https://shop.example/api/productsЯкщо сторінка намагається звернутися до іншого джерела:
fetch("https://api.example/data")
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error("Браузер не дозволив прочитати відповідь:", error);
});сервер api.example має явно дозволити такий доступ за допомогою механізму CORS.
CORS (Cross-Origin Resource Sharing) — це механізм, за допомогою якого сервер повідомляє браузеру, які інші джерела можуть читати його відповіді.
Наприклад, сервер API може повернути заголовок:
Access-Control-Allow-Origin: https://shop.exampleЦе означає:
Сторінка з джерела
https://shop.exampleможе прочитати відповідь цього сервера.
Після цього JavaScript у браузері зможе виконати кросджерельний запит:
fetch("https://api.example/data")
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP-помилка: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log("Отримані дані:", data);
})
.catch((error) => {
console.error("Не вдалося отримати дані:", error);
});CORS налаштовується на сервері. Додати заголовок Access-Control-Allow-Origin лише у frontend-коді неможливо.
Символ * дозволяє запити з будь-якого джерела:
Access-Control-Allow-Origin: *Це може бути доречно для публічних даних, але не слід використовувати такий дозвіл бездумно для приватної інформації.
Якщо запит використовує облікові дані, наприклад cookies, сервер не може одночасно вказати:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: trueДля запитів із cookies потрібно вказати конкретне дозволене джерело.
Cookies мають власні правила області дії. Вони можуть бути доступними для певного домену або його піддоменів залежно від атрибутів cookie.
Однак наявність cookie не скасовує Same-Origin Policy.
Наприклад:
браузер може автоматично додати cookie до запиту;
але JavaScript іншого джерела все одно не зможе прочитати відповідь без правильного CORS;
атрибути SameSite, Secure і HttpOnly додатково впливають на поведінку cookies.
HttpOnly особливо важливий: така cookie надсилається браузером, але недоступна через document.cookie.
postMessageІноді різним джерелам потрібно безпечно обмінюватися повідомленнями. Для цього використовується window.postMessage().
Сторінка-відправник:
const frame = document.querySelector("#payment-frame");
frame.contentWindow.postMessage(
{
type: "payment-started",
orderId: "order-123"
},
"https://payments.example"
);Сторінка-отримувач:
window.addEventListener("message", (event) => {
if (event.origin !== "https://shop.example") {
return;
}
if (event.data?.type === "payment-started") {
console.log("Почато оплату замовлення:", event.data.orderId);
}
});Перевірка event.origin є обов’язковою. Не слід приймати повідомлення від будь-якого джерела без перевірки.
Небезпечний варіант:
window.addEventListener("message", (event) => {
// Не робіть так: джерело повідомлення не перевіряється.
processPayment(event.data);
});Правильний обробник перевіряє:
event.origin;
структуру event.data;
допустимі значення полів;
за потреби — event.source.
http і https — різні джерелаНавіть якщо домен однаковий, ці адреси мають різні джерела:
http://example.com
https://example.comСхема є частиною origin. Тому перехід із HTTP на HTTPS змінює джерело.
HTTPS також захищає дані під час передавання мережею, але сам по собі не дозволяє кросджерельний доступ. Для доступу між різними джерелами все одно можуть знадобитися CORS або postMessage.
До origin не входять:
шлях;
рядок запиту;
фрагмент після #;
ім’я користувача або пароль у URL.
Наприклад, ці URL мають однакове джерело:
https://example.com/page
https://example.com/page?user=1
https://example.com/page#section
https://example.com/other-pageУ них однакові:
https://example.comАле Same-Origin Policy може застосовуватися до всього документа, тому однакове джерело не означає автоматично однаковий стан або однаковий URL.
localhostПід час розробки часто використовують:
http://localhost:3000
http://localhost:5173Це різні джерела через різні порти.
Тому frontend на localhost:5173 може отримати CORS-помилку під час запиту до API на localhost:3000. Для браузера це не «один локальний комп’ютер», а два різні джерела.
Те саме стосується IP-адрес:
http://127.0.0.1:3000
http://localhost:3000У багатьох ситуаціях вони також розглядаються як різні хости, отже це різні джерела.
https://example.com
https://api.example.comПіддомени різні, тому джерела різні.
http://localhost:3000
http://localhost:4000Однаковий хост не компенсує різні порти.
CORS не вимикає Same-Origin Policy. Він є контрольованим винятком, коли сервер дозволяє браузеру передати відповідь JavaScript-коду іншого джерела.
mode: "no-cors" і очікувати звичайну відповідьfetch("https://api.example/data", {
mode: "no-cors"
});Такий режим не дає JavaScript повноцінно прочитати відповідь. Результат буде непрозорим (opaque), тому зазвичай не можна викликати response.json() і отримати дані.
Сервер може бути доступним і навіть успішно обробляти запит, але браузер не дозволяє JavaScript прочитати відповідь через відсутній або неправильний CORS-заголовок.
postMessage без перевірки event.originПовідомлення можуть надходити з будь-якого джерела. Отримувач повинен перевіряти джерело та вміст повідомлення.
CORS-заголовки надсилає сервер. Якщо сервер не дозволяє доступ, зміна JavaScript-коду на клієнті не замінить серверне налаштування.
Джерело складається зі схеми, хоста та порту.
Шлях, query-параметри та фрагмент не входять до джерела.
http і https є різними джерелами.
Різні порти або піддомени також утворюють різні джерела.
Same-Origin Policy не дозволяє скрипту довільно читати дані іншого джерела.
Ресурс іноді можна вбудувати, але це не означає, що його можна прочитати через JavaScript.
Для кросджерельних fetch та XMLHttpRequest сервер може надати дозвіл через CORS.
Для обміну повідомленнями між вікнами та iframe використовується postMessage.
Дані з postMessage потрібно приймати лише після перевірки event.origin і структури повідомлення.