Как включить CORS с HTTPOnly Cookie для защиты токена?

Опубликовано: 2021-12-29

В этой статье мы увидим, как включить CORS (Cross-Origin Resource Sharing) с помощью HTTPOnly cookie для защиты наших токенов доступа.

В настоящее время внутренние серверы и внешние клиенты развернуты в разных доменах. Поэтому сервер должен включить CORS, чтобы клиенты могли связываться с сервером в браузерах.

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

Почему блокируются запросы Cross-Origin?

Предположим, что наше веб-приложение развернуто на https://app.geekflare.com . Скрипт, загруженный на https://app.geekflare.com может запрашивать только ресурсы того же происхождения.

Каждый раз, когда мы пытаемся отправить запрос на другой домен https://api.geekflare.com или другой порт https://app.geekflare.com:3000 или другую схему http://app.geekflare.com , запрос кросс-происхождения будет заблокирован браузером.

Но почему тот же запрос, заблокированный браузером, отправляется с любого внутреннего сервера с помощью запроса curl или отправляется с помощью таких инструментов, как почтальон, без каких-либо проблем с CORS. На самом деле это для безопасности, чтобы защитить пользователей от атак, таких как CSRF (подделка межсайтовых запросов).

Давайте возьмем пример, предположим, что какой-либо пользователь вошел в свою учетную запись PayPal в своем браузере. Если мы можем послать запрос кросс-происхождения в paypal.com из сценария , загруженного на другой домен malicious.com без ошибок CORS / блокировки , как мы посылаем запрос в тот же происхождения.

Злоумышленники могут легко отправить свою вредоносную страницу https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account , преобразовав ее в короткий URL-адрес, чтобы скрыть фактический URL-адрес. Когда пользователь щелкает вредоносную ссылку, скрипт, загруженный в домен malicious.com , отправляет в PayPal межпроцессный запрос на перевод суммы пользователя на учетную запись злоумышленника. Будет выполнен PayPal. Все пользователи, которые вошли в свою учетную запись PayPal и щелкнули по этой вредоносной ссылке, потеряют свои деньги. Любой может легко украсть деньги без ведома пользователя учетной записи PayPal.

По указанной выше причине браузеры блокируют все запросы из разных источников.

Что такое CORS (совместное использование ресурсов между источниками)?

CORS - это механизм безопасности на основе заголовков, используемый сервером, чтобы сообщить браузеру об отправке запроса между источниками из доверенных доменов.
На сервере включены заголовки CORS, используемые во избежание блокировки браузерами запросов из разных источников.

Как работает CORS?

Поскольку сервер уже определил свой доверенный домен в своей конфигурации CORS. Когда мы отправляем запрос на сервер, ответ сообщит браузеру, что запрошенный домен является доверенным или нет, в его заголовке.

Существуют два типа запросов CORS:

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

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

Поток запросов CORS-simple сообщает, что он отправляет запрос из разных источников, но когда он получил ответ. Он проверяет заголовки.

  • Браузер отправляет запрос в домен с перекрестным происхождением с источником (https://app.geekflare.com).
  • Сервер отправляет обратно соответствующий ответ с разрешенными методами и разрешенным источником.
  • После получения запроса браузер проверит, что отправленное значение заголовка origin ( https://app.geekflare.com ) и полученное значение access-control-allow-origin ( https://app.geekflare.com ) совпадают или подстановочный знак (*). В противном случае будет выдана ошибка CORS.

Предполетный запрос:

CORS-Preflight Request Image which show the flow of cross-origin request with OPTIONS preflight request before sending actual request for verifying headers.

  • В зависимости от настраиваемого параметра запроса из запроса с перекрестным происхождением, такого как методы (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.geekflare.com) или подстановочного знака (*)
  • Access-Control-Allow-Methods: разрешить HTTP-методы (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS), которые нужны только нам.
  • Access-Control-Allow-Headers: разрешить только определенные заголовки (авторизация, csrf-token)
  • Access-Control-Allow-Credentials: логическое значение, используемое для разрешения учетных данных из разных источников (файлы cookie, заголовок авторизации).
  • Access-Control-Max-Age: указывает браузеру на некоторое время кэшировать предполетный ответ.
  • Access-Control-Expose-Headers: укажите заголовки, доступные для клиентского скрипта.

Чтобы включить CORS на веб-сервере apache и Nginx, следуйте этому руководству.

Включение CORS в ExpressJS

Возьмем пример приложения ExpressJS без CORS:

 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') })

В приведенном выше примере мы включили конечную точку пользовательского API для методов POST, PUT, GET, но не для метода DELETE.

Чтобы упростить включение CORS в приложении ExpressJS, вы можете установить cors

 npm install cors

Доступ-Контроль-Разрешить-Происхождение

Включение CORS для всего домена

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

Включение CORS для одного домена

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

Если вы хотите разрешить CORS для происхождения https://app.geekflare.com и https://lab.geekflare.com

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

Доступ-Контроль-Разрешить-Методы

Чтобы включить CORS для всех методов, опустите этот параметр в модуле CORS в ExpressJS. Но для включения определенных методов (GET, POST, PUT).

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

Доступ-Контроль-Разрешить-Заголовки

Используется, чтобы разрешить отправку заголовков, отличных от значений по умолчанию, с фактическими запросами.

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

Доступ-Контроль-Разрешить-Учетные данные

Опустите это, если вы не хотите указывать браузеру разрешать учетные данные по запросу, даже если для параметра 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 }));

Доступ-Контроль-Макс-Возраст

Чтобы заставить браузер кэшировать информацию предполетного ответа в кеше на указанную секунду. Опустите это, если не хотите кэшировать ответ.

 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 }));

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

Access-Control-Expose-Заголовки

 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'] }));

Если мы поместим подстановочный знак (*) в visibleHeaders, он не будет отображать заголовок авторизации. Итак, мы должны явно раскрыть, как показано ниже

 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', ] }));

Вышеупомянутое покажет все заголовки и заголовок авторизации.

Что такое HTTP-куки?

Файл cookie - это небольшой фрагмент данных, который сервер отправляет клиентскому браузеру. При последующих запросах браузер будет отправлять все файлы cookie, относящиеся к одному и тому же домену, по каждому запросу.

Cookie имеет свой атрибут, который можно определить, чтобы cookie работал по-другому, как нам нужно.

  • Имя Имя файла cookie.
  • значение: данные файла cookie, соответствующие имени файла cookie
  • Домен: файлы cookie будут отправляться только в указанный домен.
  • Путь: файлы cookie отправляются только после указанного пути префикса URL. Предположим, мы определили наш путь для файлов cookie как path = 'admin /'. Файлы cookie не отправляются для URL-адреса https://geekflare.com/expire/, но отправляются с префиксом URL-адреса https://geekflare.com/admin/
  • Max-Age / Expires (число в секундах): когда истекает срок действия cookie. Время жизни файла cookie делает его недействительным по истечении указанного времени.
  • HTTPOnly (Boolean): внутренний сервер может получить доступ к этому cookie HTTPOnly, но не к клиентскому скрипту, если он имеет значение true.
  • Безопасный (логический): файлы cookie отправляются только через домен SSL / TLS, если истинно.
  • sameSite (string [Strict, Lax, None]): используется для включения / ограничения файлов cookie, отправляемых по межсайтовым запросам. Чтобы узнать больше о куки- sameSite см. MDN. Он принимает три варианта: Strict, Lax, None. Безопасное значение cookie установлено в значение true для конфигурации cookie sameSite = None.

Почему HTTPOnly cookie для токенов?

Хранение токена доступа, отправленного с сервера, в хранилище на стороне клиента, таком как локальное хранилище , индексированная БД и cookie (для HTTPOnly не задано значение true), более уязвимы для XSS-атак. Предположим, что какая-либо из ваших страниц уязвима для XSS-атаки. Злоумышленники могут злоупотреблять пользовательскими токенами, хранящимися в браузере.

Файлы cookie HTTPOnly устанавливаются / получаются только сервером / бэкэнд, но не на стороне клиента.

Клиентский скрипт ограничен доступом к этому куки-файлу HTTPonly. Таким образом, файлы cookie HTTPOnly не уязвимы для атак XSS и более безопасны. Потому что он доступен только на сервере.

Включение HTTPOnly cookie в серверной части с поддержкой CORS

Для включения cookie в CORS требуется указанная ниже конфигурация в приложении / сервере.

  • Установите для заголовка Access-Control-Allow-Credentials значение true.
  • Access-Control-Allow-Origin и Access-Control-Allow-Headers не должны быть подстановочными знаками (*).
  • Атрибут cookie sameSite не должен иметь значения None.
  • Чтобы включить для параметра sameSite значение none, установите для параметра secure значение true: включить серверную часть с сертификатом SSL / TLS для работы в доменном имени.

Давайте посмотрим на пример кода, который устанавливает токен доступа в HTTPOnly cookie после проверки учетных данных для входа.

 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 CORS и HTTPOnly, выполнив четыре вышеуказанных шага на своем внутреннем языке и на веб-сервере.

Вы можете следовать этому руководству для apache и Nginx, чтобы включить CORS, выполнив указанные выше действия.

withCredentials для запроса Cross-Origin

Учетные данные (cookie, авторизация) по умолчанию отправляются с запросом того же происхождения. Для кросс-происхождения мы должны указать для параметра withCredentials значение true.

XMLHttpRequest API:

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

Получить API:

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

JQuery Ajax:

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

Аксиос:

 axios.defaults.withCredentials = true

Вывод

Я надеюсь, что приведенная выше статья поможет вам понять, как работает CORS, и включить CORS для запросов из разных источников на сервере. Почему хранение файлов cookie в HTTPOnly безопасно и как withCredentials используется в клиентах для запросов из разных источников.