Як увімкнути CORS за допомогою HTTPOnly Cookie для захисту маркера?

У цьому матеріалі ми розглянемо, як активувати механізм CORS (спільне використання ресурсів між різними джерелами) разом із файлами cookie HTTPOnly для посилення безпеки наших токенів доступу.

В сучасних умовах сервери та клієнтські додатки часто функціонують у різних доменних просторах. Тому для забезпечення можливості взаємодії між клієнтами і сервером у веб-браузерах, сервер повинен підтримувати CORS.

Крім цього, для підвищення масштабованості сервери впроваджують безсесійну аутентифікацію. Токени зберігаються на стороні клієнта, а не на сервері, як у випадку сесій. Для покращення безпеки рекомендується використовувати файли cookie HTTPOnly для зберігання токенів.

Чому міждоменні запити блокуються?

Припустимо, що наш клієнтський додаток розташовано на https://app.techukraine.net.com. Скрипт, завантажений з https://app.techukraine.net.com, може звертатися лише до ресурсів з того ж самого домену.

Коли ми намагаємося відправити міждоменний запит до іншого домену, наприклад https://api.techukraine.net.com, іншого порту https://app.techukraine.net.com:3000, або навіть іншої схеми http://app.techukraine.net.com, браузер блокує цей запит.

Цікаво, що той самий запит, заблокований браузером, успішно виконується за допомогою curl або таких інструментів, як Postman. Це тому, що механізм CORS – це запобіжний захід, який браузери застосовують для захисту користувачів від атак типу CSRF (міжсайтова підробка запитів).

Розгляньмо приклад: користувач увійшов до свого PayPal-акаунту. Якщо ми могли б відправляти міждоменні запити до paypal.com зі скрипту, завантаженого з malicious.com, без блокування CORS, зловмисники могли б легко вкрасти кошти.

Зловмисники можуть створити шкідливу сторінку на https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account, приховуючи її справжню адресу під короткою URL. Коли користувач переходить за посиланням, скрипт на malicious.com відправляє міждоменний запит до PayPal, щоб переказати кошти на рахунок зловмисника. Усі користувачі, які увійшли в свій PayPal і перейшли за посиланням, втратять гроші. Це стає можливим через відсутність захисту CORS.

Саме з цих міркувань безпеки браузери блокують усі міждоменні запити.

Що таке CORS (Cross-Origin Resource Sharing)?

CORS — це механізм безпеки, що базується на заголовках, який дозволяє серверу інструктувати браузер, які міждоменні запити з довірених джерел є дозволеними.
Сервер конфігурує CORS-заголовки для запобігання блокуванню міждоменних запитів браузерами.

Як працює CORS?

Сервер заздалегідь визначає довірені домени в своїй конфігурації CORS. Коли клієнт надсилає запит, сервер у відповіді містить заголовок, який інформує браузер, чи є домен клієнта довіреним.

Існує два види CORS-запитів:

  • Простий запит
  • Передпольотний запит

Простий запит:

  • Браузер відправляє запит до міждоменного ресурсу (наприклад, з https://app.techukraine.net.com).
  • Сервер відповідає, вказуючи дозволені методи і походження.
  • Браузер перевіряє, чи збігається значення заголовка ‘origin’ (https://app.techukraine.net.com) зі значенням ‘access-control-allow-origin’ (https://app.techukraine.net.com), або чи є останнім символ підстановки *.

Якщо перевірка не пройде, браузер поверне помилку CORS.

  • Передпольотний запит:
  • Якщо запит містить спеціальні параметри, такі як методи (PUT, DELETE), спеціальні заголовки або інший тип вмісту, браузер спочатку відправляє запит OPTIONS для перевірки, чи безпечно відправляти основний запит.

Після отримання відповіді (статус 204, що означає “без вмісту”), браузер аналізує параметри ‘access-control-allow’ для фактичного запиту. Якщо параметри дозволені сервером, браузер дозволяє відправлення та отримання міждоменного запиту.

Якщо access-control-allow-origin: *, відповідь дозволена для всіх джерел, але це небезпечно, якщо не потрібно.

Як налаштувати CORS?

Щоб активувати CORS для будь-якого домену, необхідно налаштувати CORS-заголовки, щоб дозволити походження, методи, спеціальні заголовки та облікові дані.

  • Браузер читає CORS-заголовки з сервера і дозволяє запити від клієнта лише після перевірки цих параметрів.
  • Access-Control-Allow-Origin: вказує конкретні домени (наприклад, https://app.geekflate.com, https://lab.techukraine.net.com) або символ підстановки *.
  • Access-Control-Allow-Methods: дозволяє необхідні HTTP-методи (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS).
  • Access-Control-Allow-Headers: дозволяє лише певні заголовки (наприклад, ‘authorization’, ‘csrf-token’).
  • Access-Control-Allow-Credentials: логічне значення, яке дозволяє обмін обліковими даними (файли cookie, заголовок авторизації).

Access-Control-Max-Age: вказує браузеру, на який час потрібно кешувати відповіді на попередні запити.

Access-Control-Expose-Headers: визначає, які заголовки відповіді доступні для клієнтського скрипту.

Для налаштування CORS на веб-серверах Apache та Nginx скористайтеся наведеними інструкціями.

const express = require('express');
const app = express()

app.get('/users', function (req, res, next) {
  res.json({msg: 'user get'})
});

app.post('/users', function (req, res, next) {
    res.json({msg: 'user create'})
});

app.put('/users', function (req, res, next) {
    res.json({msg: 'User update'})
});

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

Активування CORS у ExpressJS

Розглянемо приклад застосунку ExpressJS без CORS:

npm install cors

У наведеному прикладі ми активували кінцеву точку API користувачів для методів POST, PUT, GET, але не для DELETE.

Для легкого налаштування CORS у ExpressJS, встановіть модуль cors:

app.use(cors({
    origin: '*'
}));

Access-Control-Allow-Origin

app.use(cors({
    origin: 'https://app.techukraine.net.com'
}));

Активування CORS для всіх доменів

app.use(cors({
    origin: [
        'https://app.geekflare.com',
        'https://lab.geekflare.com'
    ]
}));

Активування CORS для одного домену

Якщо ви хочете дозволити CORS для джерел https://app.techukraine.net.com і https://lab.techukraine.net.com

app.use(cors({
    origin: [
        'https://app.geekflare.com',
        'https://lab.geekflare.com'
    ],
    methods: ['GET', 'PUT', 'POST']
}));

Access-Control-Allow-Methods

Для активування CORS для всіх методів, пропустіть цей параметр в модулі cors для ExpressJS. Для активації лише певних методів (наприклад, GET, POST, PUT) використовуйте наведений приклад.

app.use(cors({
    origin: [
        'https://app.geekflare.com',
        'https://lab.geekflare.com'
    ],
    methods: ['GET', 'PUT', 'POST'],
    allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token']
}));

Access-Control-Allow-Headers

Використовується для надсилання нестандартних заголовків із фактичними запитами.

app.use(cors({
    origin: [
        'https://app.geekflare.com',
        'https://lab.geekflare.com'
    ],
    methods: ['GET', 'PUT', 'POST'],
    allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
    credentials: true
}));

Access-Control-Allow-Credentials

Пропустіть цей параметр, якщо ви не хочете дозволяти браузеру обмінюватись обліковими даними у запиті, навіть якщо для параметра ‘withCredentials’ встановлено значення true.

app.use(cors({
    origin: [
        'https://app.geekflare.com',
        'https://lab.geekflare.com'
    ],
    methods: ['GET', 'PUT', 'POST'],
    allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
    credentials: true,
    maxAge: 600 
}));

Access-Control-Max-Age

Дозволяє браузеру кешувати інформацію відповіді протягом вказаної кількості секунд. Пропустіть, якщо ви не хочете кешувати відповідь.

app.use(cors({
    origin: [
        'https://app.geekflare.com',
        'https://lab.geekflare.com'
    ],
    methods: ['GET', 'PUT', 'POST'],
    allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
    credentials: true,
    maxAge: 600,
    exposedHeaders: ['Content-Range', 'X-Content-Range']
}));

Кешована відповідь перед попереднім запитом буде доступна 10 хвилин у браузері.

app.use(cors({
    origin: [
        'https://app.geekflare.com',
        'https://lab.geekflare.com'
    ],
    methods: ['GET', 'PUT', 'POST'],
    allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
    credentials: true,
    maxAge: 600,
    exposedHeaders: ['*', 'Authorization', ]
}));

Access-Control-Expose-Headers

Якщо поставити символ підстановки *

у ‘exposedHeaders’, заголовок ‘authorization’ не буде доступний. Тому необхідно вказати явно, як показано нижче

Наведений вище приклад відкриє всі заголовки і заголовок ‘authorization’.

  • Що таке HTTP cookie?
  • Файли cookie — це невеликі фрагменти даних, які сервер надсилає у браузер клієнта. У подальших запитах браузер надсилає файли cookie, пов’язані з доменом, у кожному запиті.
  • Cookie має атрибути, які визначають її поведінку.
  • Ім’я: Назва файлу cookie.
  • Значення: дані файлу cookie.
  • Домен: файли cookie надсилаються тільки для вказаного домену.
  • Шлях: файли cookie надсилаються тільки для запитів, що мають вказаний префікс URL. Наприклад, якщо шлях cookie встановлено як path=’/admin/’, файли cookie не надсилатимуться для https://techukraine.net.com/expire/, але будуть надсилатись для https://techukraine.net.com/admin/.
  • Максимальний вік/Термін дії (у секундах): визначає термін дії файлу cookie. Cookie стане недійсним після закінчення зазначеного часу. [Strict, Lax, None]HTTPOnly (Boolean): якщо true, сервер має доступ до файлу cookie HTTPOnly, а не клієнтський скрипт. Secure (логічний): файли cookie відправляються лише через SSL/TLS, якщо true.sameSite (рядок

): використовується для керування відправкою файлів cookie міжсайтовим запитам. Детальніше про sameSite cookies можна дізнатись на MDN

. Приймає значення Strict, Lax, None. Для налаштування sameSite=None, значення Secure для cookie має бути true.

Чому HTTPOnly cookie для токенів?

Зберігання токена доступу у локальному сховищі, базі даних чи файлах cookie (якщо HTTPOnly не true) підвищує вразливість до атак XSS. Якщо на сторінці є вразливість XSS, зловмисники можуть викрасти токени, що зберігаються в браузері.

HTTPOnly файли cookie встановлюються та отримуються лише сервером, але не клієнтом.

  • Клієнтські скрипти обмежені в доступі до HTTPOnly файлів cookie. Тому, вони менш вразливі до атак XSS і є більш безпечними, оскільки доступні тільки серверу.
  • Активуйте HTTPOnly cookie у підтримці CORS
  • Для підтримки cookie в CORS потрібна наступна конфігурація на сервері.
  • Встановіть значення Access-Control-Allow-Credentials у true.

Заголовки Access-Control-Allow-Origin та Access-Control-Allow-Headers не мають використовувати символ підстановки *.

const express = require('express'); 
const app = express();
const cors = require('cors');

app.use(cors({ 
  origin: [ 
    'https://app.geekflare.com', 
    'https://lab.geekflare.com' 
  ], 
  methods: ['GET', 'PUT', 'POST'], 
  allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], 
  credentials: true, 
  maxAge: 600, 
  exposedHeaders: ['*', 'Authorization' ] 
}));

app.post('/login', function (req, res, next) { 
  res.cookie('access_token', access_token, {
    expires: new Date(Date.now() + (3600 * 1000 * 24 * 180 * 1)), //second min hour days year
    secure: true, // set to true if your using https or samesite is none
    httpOnly: true, // backend only
    sameSite: 'none' // set to none for cross-request
  });

  res.json({ msg: 'Login Successfully', access_token });
});

app.listen(80, function () { 
  console.log('CORS-enabled web server listening on port 80') 
}); 

.

Атрибут файлу cookie ‘sameSite’ повинен мати значення ‘None’.

Щоб використовувати ‘sameSite=None’, необхідно встановити ‘secure=true’, та активувати сервер з SSL/TLS сертифікатом.

Розглянемо приклад коду, який встановлює токен доступу в HTTPOnly cookie після перевірки облікових даних для входу.

Ви можете налаштувати CORS та HTTPOnly cookie, виконавши чотири наведені вище кроки на сервері.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://api.techukraine.net.com/user', true);
xhr.withCredentials = true;
xhr.send(null);

Ви можете використати цю інструкцію для налаштування CORS на Apache та Nginx.

fetch('http://api.techukraine.net.com/user', {
  credentials: 'include'
});

з обліковими даними для міждоменного запиту

$.ajax({
   url: 'http://api.techukraine.net.com/user',
   xhrFields: {
      withCredentials: true
   }
});

Облікові дані (файли cookie, авторизація) надсилаються за замовчуванням з запитами в межах одного домену. Для міждоменних запитів потрібно вказати withCredentials=true.

axios.defaults.withCredentials = true

API XMLHttpRequest

API Fetch

JQuery Ajax

Axios

Висновок: Сподіваємося, ця стаття допомогла вам зрозуміти, як працює CORS та як увімкнути його для міждоменних запитів. Також ми розглянули, чому зберігання файлів cookie у HTTPOnly є безпечним і як використовувати облікові дані в міждоменних запитах.