Post preflight cors error

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

Время прочтения
8 мин

Просмотры 12K

В этой статье мы с вами разберемся, что такое CORS, CORS-ошибки и из-за чего мы можем с ними сталкиваться. Я также продемонстрирую возможные решения и объясню, что такое предварительные (preflight) запросы, CORS-заголовки и в чем заключается их важность при обмене данными между сторонами. Эта статья рассчитана на тех, у кого уже есть базовые познания в области веб-разработки и некоторый опыт с протоколом HTTP. Я старался писать статью так, чтобы она была понятна и новичкам, будучи наполненной знаниями, но при этом стараясь избегать слишком большого количества технических нюансов, не связанных с темой CORS. Если вы заметите какие-либо ошибки или у вас будут предложения, не стесняйтесь писать мне. В некоторых местах я нарочно делал упрощения, говоря “служба”, подразумевая “сервер”, и наоборот.

Что такое CORS?

Cross-Origin Resource Sharing (CORS или “совместное использование ресурсов различными источниками”) — это контролируемый и применяемый в принудительном порядке клиентом (браузером) механизм обеспечения безопасности на основе HTTP. Он позволяет службе (API) указывать любой источник (origin), помимо себя, из которого клиент может запрашивать ресурсы. Он был разработан в соответствии с same-origin policy (SOP или “политика одинакового источника”), которая ограничивает взаимодействие сайта (HTML-документа или JS-скрипта), загруженного из одного источника, с ресурсом из другого источника. CORS используется для явного разрешения определенных cross-origin запросов и отклонения всех остальных.

В основном CORS реализуют веб-браузеры, но как вариант его также можно использовать в API-клиентах. Он присутствует во всех популярных браузерах, таких как Google Chrome, Firefox, Opera и Safari. Этот стандарт был принят в качестве рекомендации W3C в январе 2014 года. Исходя из этого, можно смело предполагать, что он реализован во всех доступных в настоящее время браузерах, которые не были перечислены выше.

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

Все начинается на стороне клиента, еще до отправки основного запроса. Клиент отправляет в службу с ресурсами предварительный (preflight) CORS-запрос с определенными параметрами в заголовках HTTP (CORS-заголовках, если быть точнее). Служба отвечает, используя те же заголовки с другими или такими же значениями. На основе ответа на предварительный CORS-запрос клиент решает, может ли он отправить основной запрос к службе. Браузер (клиент) выдаст ошибку, если ответ не соответствует требованиям предварительной проверки CORS.

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

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

Что такое предварительная проверка CORS?

Когда браузер отправляет запрос на сервер, он сначала отправляет Options HTTP-запрос. Это и есть предварительным CORS-запрос. Затем сервер отвечает списком разрешенных методов и заголовков. Если браузеру разрешено сделать фактический запрос, то тогда он незамедлительно отправит его. Если нет, он покажет пользователю ошибку и не выполнит основной запрос.

CORS-заголовки

CORS-заголовки — это обычные заголовки HTTP, которые используются для контроля политики CORS. Они используются, когда браузер отправляет предварительный CORS-запрос на сервер, на который сервер отвечает следующими заголовками:

  • Access-Control-Allow-Origin указывает, какой источник может получать ресурсы. Вы можете указать один или несколько источников через запятую, например: https://foo.io,http://bar.io.

  • Access-Control-Allow-Methods указывает, какие HTTP-методы разрешены. Вы можете указать один или несколько HTTP-методов через запятую, например: GET,PUT,POST.

  • Access-Control-Allow-Headers указывает, какие заголовки запросов разрешены. Вы можете указать один или несколько заголовков через запятую, например: Authorization,X-My-Token.

  • Access-Control-Allow-Credentials указывает, разрешена ли отправка файлов cookie. По умолчанию: false.

  • Access-Control-Max-Age указывает в секундах, как долго должен кэшироваться результат запроса. По умолчанию: 0.

Если вы решите использовать Access-Control-Allow-Credentials=true, то вам нужно знать, что вы не сможете использовать символы * в заголовках Access-Control-Allow-*. Необходимо будет явно перечислить все разрешенные источники, методы и заголовки.

Полный список CORS-заголовков вы можете найти здесь.

Почему запрос может быть заблокирован политикой CORS?

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

Access to XMLHttpRequest at 'http://localhost:8080/' from origin
'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control
check: No 'Access-Control-Allow-Origin' header is present
on the requested resource.

Приведенная выше CORS-ошибка уведомляет пользователя о том, что браузер не может получить доступ к ресурсу (https://localhost:8080) из источника (https://localhost:3000), поскольку сервер его не одобрил. Это произошло из-за того, что сервер не ответил заголовком Access-Control-Allow-Origin с этим источником или символом * в ответе на предварительный CORS-запрос.

Запрос может быть заблокирован политикой CORS не только из-за невалидного источника, но и из-за неразрешенных заголовка HTTP, HTTP-метода или заголовка Cookie.

Как исправить CORS-ошибку?

Фундаментальная идея “исправления CORS” заключается в том, чтобы отвечать на OPTIONS запросы, отправленные от клиента, корректными заголовками. Есть много способов начать отвечать корректными CORS. Вы можете использовать прокси-сервер или какое-нибудь middleware на своем сервере.

Помните, что заголовки Access-Control-* кэшируются в браузере в соответствии со значением, установленным в заголовке Access-Control-Max-Age. Поэтому перед тестированием изменений вам обязательно нужно чистить кэш. Вы также можете отключить кэширование в своем браузере.

1. Настройка вашего сервера

По умолчанию, если вы являетесь владельцем сервера, вам необходимо настроить на своем сервере CORS-ответы, и это единственный способ правильно решить проблему. Вы можете добиться этого несколькими способами из нескольких слоев вашего приложения. Самый распространенный способ — использовать обратный прокси-сервер (reverse-proxy), API-шлюз или любой другой сервис маршрутизации, который позволяет добавлять заголовки к ответам. Для этого можно использовать множество сервисов, и вот некоторые из них: HAProxy, Linkerd, Istio, Kong, nginx, Apache, Traefik. Если ваша инфраструктура содержит только приложение без каких-либо дополнительных слоев, то вы можете добавить поддержку CORS в код самого приложения.

Вот несколько популярных примеров активации CORS:

  • Apache: отредактируйте файл .htaccess

  • Nginx: отредактируйте файл конфигурации,

  • Traefik: используйте middleware,

  • Spring Boot: используйте аннотацию @EnableCORS,

  • ExpressJS: используйте app.use(cors()),

  • NextJS: используйте реквест-хелперы.

Здесь вы можете найти больше примеров активации CORS для разных фреймворков и языков: enable-cors.org.

Если вы не можете активировать CORS в службе, но все же хотите сделать возможными запросы к ней, то вам нужно использовать одно из следующих решений, описанных ниже.

2. Установка расширения для браузера

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

Расширения для браузера изменяют входящий предварительный запрос, добавляя необходимые заголовки, чтобы обмануть браузер. Это очень удобное решение для локальной работы с производственным API, которое принимает запросы только из рабочего домена.

Вы можете найти расширения в Google Web Store или в библиотеке дополнений Mozilla. В некоторых случаях дефолтной конфигурации расширения может быть недостаточно; убедитесь, что установленное расширение корректно настроено. Вам также следует быть в курсе, что если держать подобное расширение включенным постоянно, то это может вызвать проблемы с некоторыми сайтами. Их рекомендуется использовать только в целях разработки.

3. Отключение CORS-проверок в браузере

Вы можете полностью отключить CORS-проверки в своем браузере. Чтобы отключить CORS-проверки в Google Chrome, нужно закрыть браузер и запустить его с флагами --disable-web-security и --user-data-dir. После запуска Google Chrome не будет отправлять предварительные CORS-запросы и не будет проверять CORS-заголовки.

# Windows
chrome.exe --user-data-dir="C://chrome-dev-disabled-security" --disable-web-security --disable-site-isolation-trials

# macOS
open /Applications/Google Chrome.app --args --user-data-dir="/var/tmp/chrome-dev-disabled-security" --disable-web-security --disable-site-isolation-trials

# Linux
google-chrome --user-data-dir="~/chrome-dev-disabled-security" --disable-web-security --disable-site-isolation-trials

Все команды, приведенные выше, запускают Google Chrome в изолированной безопасной среде. Они не затронут ваш основной профиль Chrome.

Список всех доступных флагов для Google Chrome можно найти здесь.

4. Настройка прокси-сервера

Если вы ищете решение, которое не требует от вас изменения настроек браузера, вам следует обратить внимание на прокси-сервера. Эта опция поможет вам избавиться от CORS-ошибок, ничего не меняя в самом браузере. Идея подхода заключается в том, чтобы отправлять все запросы на ваш сервер, который затем перенаправит запрос реальной службе, которую вы хотите использовать. Вы можете создать прокси-сервер самостоятельно с помощью языка и платформы по вашему предпочтению. Вам потребуется настроить CORS и реализовать функционал передачи полученных запросов в другую службу.

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

Ниже приведен список CORS сервисов с открытым исходным кодом, которые вы можете найти на просторах интернета:

  • https://github.com/Freeboard/thingproxy

  • https://github.com/bulletmark/corsproxy

  • https://github.com/Rob—W/cors-anywhere

Перед использованием любого из этих сервисов обязательно проверьте код самой последний версии версии.

Как протестировать CORS?

Использование браузера для проверки конфигурации CORS может оказаться на удивление утомительной задачей. В качестве альтернативы вы можете использовать такие инструменты, как CORS Tester, test-cors.org или, если вас не страшит командная строка, вы можете использовать curl для проверки конфигурации CORS.

curl -v -X OPTIONS https://simplelocalize.io/api/v1/translations

Остерегайтесь ложных CORS-ошибок

В некоторых случаях, когда сервис находится за дополнительным слоем защиты ограничителя частоты запросов, балансировщика нагрузки или сервера авторизации, вы можете получить ложную CORS-ошибку. Заблокированные или отклоненные сервером запросы должны отвечать статус кодами ошибки. Например:

  • 401 unauthorized,

  • 403 forbidden,

  • 429 too many requests,

  • 500 internal server error,

  • любые, кроме 2XX или 3XX.

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

Заключение

В этой статье я постарался объяснить, что такое CORS и каковы наиболее распространенные проблемы с ним. Я предложил четыре способа избавиться от проблемы с CORS и объяснил преимущества и недостатки каждого из них. Я также объяснил, как правильно настроить CORS-ответы и как их протестировать. Более того, я показал самые распространенные проблемы, с которыми вы можете столкнуться при распознавании ложных CORS-ошибок. Я постарался изложить все максимально простым языком и избежать мудреных технических подробностей. Cпасибо за внимание!

Полезные ссылки

  • https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors

  • https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy

  • https://enable-cors.org/

  • https://stackoverflow.com/a/42024918/1133169


Материал подготовлен в преддверии старта онлайн-курса «JavaScript Developer. Professional». Недавно прошел открытый урок на тему «CSS-in-JS. Удобный способ управлять стилями», на котором рассмотрели Styled components, Linaria, Astroturf и другие инструменты упрощения работы со стилями. Посмотреть запись можно по ссылке.

Use Case: Making your Express API accessible to receive requests.

Building an API in Express and not able to fetch data?

Trying to submit a form via a POST request and getting CORS Pre-Flight errors in the console?

That can be frustrating — especially when you see an array of questions resolved by answers in Stack Overflow that don’t seem to be working for you.

My issues began when I built a React frontend fetching data from an Express.js API. The React application sends both GET and POST http requests to the API and only now do I realize that the POST requests required an additional step.

Hey, Tyler here. I’m currently working on some great web development and digital marketing products and services over at ZeroToDigital.

If that sounds interesting to you, please check it out!

Fetching a GET request with Express

To fetch data via a GET request from Express to my React application, all I needed was a route middleware function that set the Access-Control headers for the page:

JavaScript

const headers = (req, res, next) => {
	const origin = (req.headers.origin == 'http://localhost:3000') ? 'http://localhost:3000' : 'https://mywebsite.com'
	res.setHeader('Access-Control-Allow-Origin', origin)
	res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
	res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
	res.setHeader('Access-Control-Allow-Credentials', true)
	next()
}

module.exports = headers

I would then simply reference the middleware in my route function:

JavaScript

router.get('/contact', headers, (req, res) => {
	...
	res.send(req.args)
})

Done. I could receive data all day with this setup. But the problem came with sending data back to the API with a POST request …

Fetching a POST request with Express

What I learned was that certain CORS requests are considered «complex» and require an initial options request known as a pre-flight request. From my understanding, POST requests didn’t fall within this categorization (DELETE requests are more common). However, my Express application did consider it to be complex and was therefore blocking it.

The simplest way to solve this and send a pre-flight request was via the CORS Node.js package (NPM ) — npm i cors and assigning it as middleware for the POST route:

JavaScript

const cors = require('cors')

router.post('/contact', cors(), headers, (req, res) => {
	...

The headers middleware is still required to be sent along with the actual POST request. And this solved my issue.

The CORS module can be customized, but the default configuration worked in my case.

JSON

{
	"origin": "*",
	"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
	"preflightContinue": false,
	"optionsSuccessStatus": 204
}

Hopefully this solved your problem. If not, reach out to me on Twitter below!

Many websites have JavaScript functions that make network requests to a server, such as a REST API. The web pages and APIs are often in different domains. This introduces security issues in that any website can request data from an API. Cross-Origin Resource Sharing (CORS) provides a solution to these issues. It became a W3C recommendation in 2014. It makes it the responsibility of the web browser to prevent unauthorized access to APIs. All modern web browsers enforce CORS. They prevent JavaScript from obtaining data from a server in a domain different than the domain the website was loaded from, unless the REST API server gives permission.

From a developer’s perspective, CORS is often a cause of much grief when it blocks network requests. CORS provides a number of different mechanisms for limiting JavaScript access to APIs. It is often not obvious which mechanism is blocking the request. We are going to build a simple web application that makes REST calls to a server in a different domain. We will deliberately make requests that the browser will block because of CORS policies and then show how to fix the issues. Let’s get started!

NOTE: The code for this project can be found on GitHub.

Table of Contents

  • Prerequisites to Building a Go Application
  • How to Build a Simple Web Front End
  • How to Build a Simple REST API in Go
  • How to Solve a Simple CORS Issue
  • Allowing Access from Any Origin Domain
  • CORS in Flight
  • What Else Does CORS Block?
  • Restrictions on Response Headers
  • Credentials Are a Special Case
  • Control CORS Cache Configuration
  • How to Prevent CORS Issues with Okta
  • How CORS Prevents Security Issues

Prerequisites to Building a Go Application

First things first, if you don’t already have Go installed on your computer you will need to download and install the Go Programming Language.

Now, create a directory where all of our future code will live.

Finally, we will make our directory a Go module and install the Gin package (a Go web framework) to implement a web server.

go mod init cors
go get github.com/gin-gonic/gin
go get github.com/gin-contrib/static

A file called go.mod will get created containing the dependencies.

How to Build a Simple Web Front End

We are going to build a simple HTML and JavaScript front end and serve it from a web server written using Gin.

First of all, create a directory called frontend and create a file called frontend/index.html with the following content:

<html>
    <head>
        <meta charset="UTF-8" />
        <title>Fixing Common Issues with CORS</title>
    </head>
    <body>
        <h1>Fixing Common Issues with CORS</h1>
        <div>
            <textarea id="messages" name="messages" rows="10" cols="50">Messages</textarea><br/>
            <form id="form1">
                <input type="button" value="Get v1" onclick="onGet('v1')"/>
                <input type="button" value="Get v2" onclick="onGet('v2')"/>
            </form>
        </div>
    </body>
</html>

The web page has a text area to display messages and a simple form with two buttons. When a button is clicked it calls the JavaScript function onGet() passing it a version number. The idea being that v1 requests will always fail due to CORS issues, and v2 will fix the issue.

Next, create a file called frontend/control.js containing the following JavaScript:

function onGet(version) {
    const url = "http://localhost:8000/api/" + version + "/messages";
    var headers = {}
    
    fetch(url, {
        method : "GET",
        mode: 'cors',
        headers: headers
    })
    .then((response) => {
        if (!response.ok) {
            throw new Error(response.error)
        }
        return response.json();
    })
    .then(data => {
        document.getElementById('messages').value = data.messages;
    })
    .catch(function(error) {
        document.getElementById('messages').value = error;
    });
}

The onGet() function inserts the version number into the URL and then makes a fetch request to the API server. A successful request will return a list of messages. The messages are displayed in the text area.

Finally, create a file called frontend/.go containing the following Go code:

package main

import (
	"github.com/gin-contrib/static"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.Use(static.Serve("/", static.LocalFile("./frontend", false)))
	r.Run(":8080")
}

This code simply serves the contents of the frontend directory on requests on port 8080. Note that JavaScript makes a call to port http://localhost:8000 which is a separate service.

Start the server and point a web browser at http://localhost:8080 to see the static content.

How to Build a Simple REST API in Go

Create a directory called rest to contain the code for a basic REST API.

NOTE: A separate directory is required as Go doesn’t allow two program entry points in the same directory.

Create a file called rest/server.go containing the following Go code:

package main

import (
	"fmt"
	"strconv"
	"net/http"

	"github.com/gin-gonic/gin"
)

var messages []string

func GetMessages(c *gin.Context) {
	version := c.Param("version")
	fmt.Println("Version", version)
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

func main() {
	messages = append(messages, "Hello CORS!")
	r := gin.Default()
	r.GET("/api/:version/messages", GetMessages)
	r.Run(":8000")
}

A list called messages is created to hold message objects.

The function GetMessages() is called whenever a GET request is made to the specified URL. It returns a JSON string containing the messages. The URL contains a path parameter which will be v1 or v2. The server listens on port 8000.

The server can be run using:

How to Solve a Simple CORS Issue

We now have two servers—the front-end one on http://localhost:8080, and the REST API server on http://localhost:8000. Even though the two servers have the same hostname, the fact that they are listening on different port numbers puts them in different domains from the CORS perspective. The domain of the web content is referred to as the origin. If the JavaScript fetch request specifies cors a request header will be added identifying the origin.

Origin: http://localhost:8080

Make sure both the frontend and REST servers are running.

Next, point a web browser at http://localhost:8080 to display the web page. We are going to get JavaScript errors, so open your browser’s developer console so that we can see what is going on. In Chrome it is *View** > Developer > Developer Tools.

Next, click on the Send v1 button. You will get a JavaScript error displayed in the console:

Access to fetch at ‘http://localhost:8000/api/v1/messages’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

The message says that the browser has blocked the request because of a CORS policy. It suggests two solutions. The second suggestion is to change the mode from cors to no-cors in the JavaScript fetch request. This is not an option as the browser always deletes the response data when in no-cors mode to prevent data from being read by an unauthorized client.

The solution to the issue is for the server to set a response header that allows the browser to make cross-domain requests to it.

Access-Control-Allow-Origin: http://localhost:8080

This tells the web browser that the cross-origin requests are to be allowed for the specified domain. If the domain specified in the response header matches the domain of the web page, specified in the Origin request header, then the browser will not block the response being received by JavaScript.

We are going to set the header when the URL contains v2. Change the GetMessages() function in cors/server.go to the following:

func GetMessages(c *gin.Context) {
	version := c.Param("version")
	fmt.Println("Version", version)
	if version == "v2" {
		c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	}
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

This sets a header to allow cross-origin requests for the v2 URI.

Restart the server and go to the web page. If you click on Get v1 you will get blocked by CORS. If you click on Get v2, the request will be allowed.

A response can only have at most one Access-Control-Allow-Origin header. The header can only specify only one domain. If the server needs to allow requests from multiple origin domains, it needs to generate an Access-Control-Allow-Origin response header with the same value as the Origin request header.

Allowing Access from Any Origin Domain

There is an option to prevent CORS from blocking any domain. This is very popular with developers!

Access-Control-Allow-Origin: *

Be careful when using this option. It will get flagged in a security audit. It may also be in violation of an information security policy, which could have serious consequences!

CORS in Flight

Although we have fixed the main CORS issue, there are some limitations. One of the limitations is that only the HTTP GET, and OPTIONS methods are allowed. The GET and OPTIONS methods are read-only and are considered safe as they don’t modify existing content. The POST, PUT, and DELETE methods can add or change existing content. These are considered unsafe. Let’s see what happens when we make a PUT request.

First of all, add a new form to client/index.html:

<form id="form2">
    <input type="text" id="puttext" name="puttext"/>
    <input type="button" value="Put v1" onclick="onPut('v1')"/>
    <input type="button" value="Put v2" onclick="onPut('v2')"/>
</form>

This form has a text input and the two send buttons as before that call a new JavaScript function.

Next, add the JavaScript funtion to client/control.js:

function onPut(version) {
    const url = "http://localhost:8000/api/" + version + "/messages/0";
    var headers = {}

    fetch(url, {
        method : "PUT",
        mode: 'cors',
        headers: headers,
        body: new URLSearchParams(new FormData(document.getElementById("form2"))),
    })
    .then((response) => {
        if (!response.ok) {
            throw new Error(response.error)
        }
        return response.json();
    })
    .then(data => {
        document.getElementById('messages').value = data.messages;
    })
    .catch(function(error) {
        document.getElementById('messages').value = error;
    });
}

This makes a PUT request sending the form parameters in the request body. Note that the URI ends in /0. This means that the request is to create or change the message with the identifier 0.

Next, define a PUT handler in the main() function of rest/server.go:

r.PUT("/api/:version/messages/:id", PutMessage)

The message identifier is extracted as a path parameter.

Finally, add the request handler function to rest/server.go:

func PutMessage(c *gin.Context) {
	version := c.Param("version")
	id, _ := strconv.Atoi(c.Param("id"))
	text := c.PostForm("puttext")
	messages[id] = text
	if version == "v2" {
	    c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	}
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

This updates the message from the form data and sends the new list of messages. The function also always sets the allow origin header, as we know that it is required.

Now, restart the servers and load the web page. Make sure that the developer console is open. Add some text to the text input and hit the Send v1 button.

You will see a slightly different CORS error in the console:

Access to fetch at ‘http://localhost:8000/api/v1/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

This is saying that a preflight check was made, and that it didn’t set the Access-Control-Allow-Origin header.

Now, look at the console output from the API server:

[GIN] 2020/12/01 - 11:10:09 | 404 |  447ns |  ::1 | OPTIONS  "/api/v1/messages/0"

So, what is happening here? JavaScript is trying to make a PUT request. This is not allowed by CORS policy. In the GET example, the browser made the request and blocked the response. In this case, the browser refuses to make the PUT request. Instead, it sent an OPTIONS request to the same URI. It will only send the PUT if the OPTIONS request returns the correct CORS header. This is called a preflight request. As the server doesn’t know what method the OPTIONS preflight request is for, it specifies the method in a request header:

Access-Control-Request-Method: PUT

Let’s fix this by adding a handler for the OPTIONS request that sets the allow origin header in cors/server.go:

func OptionMessage(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
}

func main() {
	messages = append(messages, "Hello CORS!")
	r := gin.Default()
	r.GET("/api/:version/messages", GetMessages)
	r.PUT("/api/:version/messages/:id", PutMessage)
	r.OPTIONS("/api/v2/messages/:id", OptionMessage)
	r.Run(":8000")
}

Notice that the OPTIONS handler is only set for the v2 URI as we don’t want to fix v1.

Now, restart the server and send a message using the Put v2 button. We get yet another CORS error!

Access to fetch at ‘http://localhost:8000/api/v2/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

This is saying that the preflight check needs another header to stop the PUT request from being blocked.

Add the response header to cors/server.go:

func OptionMessage(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	c.Header("Access-Control-Allow-Methods", "GET, OPTIONS, POST, PUT")
}

Restart the server and resend the message. The CORS issues are resolved.

What Else Does CORS Block?

CORS has a very restrictive policy regarding which HTTP request headers are allowed. It only allows safe listed request headers. These are Accept, Accept-Language, Content-Language, and Content-Type. They can only contain printable characters and some punctuation characters are not allowed. Header values can’t have more than 128 characters.

There are further restrictions on the Content-Type header. It can only be one of application/x-www-form-urlencoded, multipart/form-data, and text/plain. It is interesting to note that application/json is not allowed.

Let’s see what happens if we send a custom request header. Modify the onPut() function in frontend/control.jsto set a header:

var headers = { "X-Token": "abcd1234" }

Now, make sure that the servers are running and load or reload the web page. Type in a message and click the Put v1 button. You will see a CORS error in the developer console.

Access to fetch at ‘http://localhost:8000/api/v2/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Request header field x-token is not allowed by Access-Control-Allow-Headers in preflight response.

Any header which is not CORS safe-listed causes a preflight request. This also contains a request header that specifies the header that needs to be allowed:

Access-Control-Request-Headers: x-token

Note that the header name x-token is specified in lowercase.

The preflight response can allow non-safe-listed headers and can remove restrictions on safe listed headers:

Access-Control-Allow-Headers: X_Token, Content-Type

Next, change the function in cors/server.go to allow the custom header:

func OptionMessage(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT")
	c.Header("Access-Control-Allow-Headers", "X-Token")
}

Restart the server and resend the message by clicking the Put v2 button. The request should now be successful.

CORS also places restrictions on response headers. There are seven whitelisted response headers: Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, and Pragma. These are the only response headers that can be accessed from JavaScript. Let’s see this in action.

First of all, add a text area to frontend/index.html to display the headers:

<textarea id="headers" name="headers" rows="10" cols="50">Headers</textarea><br/>

Next, replace the first then block in the onPut function in frontend/control.js to display the response headers:

.then((response) => {
    if (!response.ok) {
        throw new Error(response.error)
    }
    response.headers.forEach(function(val, key) {
        document.getElementById('headers').value += 'n' + key + ': ' + val; 
    });
    return response.json();
})

Finally, set a custom header in rest/server.go:

func PutMessage(c *gin.Context) {
	version := c.Param("version")
	id, _ := strconv.Atoi(c.Param("id"))
	text := c.PostForm("puttext")
	messages[id] = text
	if version == "v2" {
	    c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	}
	c.Header("X-Custom", "123456789")
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

Now, restart the server and reload the web page. Type in a message and hit Put v2. You should see some headers displayed, but not the custom header. CORS has blocked it.

This can be fixed by setting another response header to expose the custom header in server/server.go:

func PutMessage(c *gin.Context) {
	version := c.Param("version")
	id, _ := strconv.Atoi(c.Param("id"))
	text := c.PostForm("puttext")
	messages[id] = text
	if version == "v2" {
        c.Header("Access-Control-Expose-Headers", "X-Custom")
	    c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
	}
	c.Header("X-Custom", "123456789")
	c.JSON(http.StatusOK, gin.H{"messages": messages})
}

Restart the server and reload the web page. Type in a message and hit Put v2. You should see some headers displayed, including the custom header. CORS has now allowed it.

Credentials Are a Special Case

There is yet another CORS blocking scenario. JavaScript has a credentials request mode. This determines how user credentials, such as cookies are handled. The options are:

  • omit: Never send or receive cookies.
  • same-origin: This is the default, that allows user credentials to be sent to the same origin.
  • include: Send user credentials even if cross-origin.

Let’s see what happens.

Modify the fetch call in the onPut() function in frontend/Control.js:

fetch(url, {
    method : "PUT",
    mode: 'cors',
    credentials: 'include',
    headers: headers,
    body: new URLSearchParams(new FormData(document.getElementById("form2"))),
})

Now, make sure that the client and server are running and reload the web page. Send a message as before. You will get another CORS error in the developer console:

Access to fetch at ‘http://localhost:8000/api/v2/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: The value of the ‘Access-Control-Allow-Credentials’ header in the response is ‘’ which must be ‘true’ when the request’s credentials mode is ‘include’.

The fix for this is to add another header to both the OptionMessage(), and PutMessage() functions in cors/server.go as the header needs to be present in both the preflight request and the actual request:

c.Header("Access-Control-Allow-Credentials", "true")

The request should now work correctly.

The credentials issue can also be resolved on the client by setting the credentials mode to omit and sending credentials as request parameters, instead of using cookies.

Control CORS Cache Configuration

The is one more CORS header that hasn’t yet been discussed. The browser can cache the preflight request results. The Access-Control-Max-Age header specifies the maximum time, in seconds, the results can be cached. It should be added to the preflight response headers as it controls how long the Access-Control-Allow-Methods and Access-Control-Allow-Headers results can be cached.

Access-Control-Max-Age: 600

Browsers typically have a cap on the maximum time. Setting the time to -1 prevents caching and forces a preflight check on all calls that require it.

How to Prevent CORS Issues with Okta

Authentication providers, such as Okta, need to handle cross-domain requests to their authentication servers and API servers. This is done by providing a list of trusted origins. See Okta Enable CORS for more information.

How CORS Prevents Security Issues

CORS is an important security feature that is designed to prevent JavaScript clients from accessing data from other domains without authorization.

Modern web browsers implement CORS to block cross-domain JavaScript requests by default. The server needs to authorize cross-domain requests by setting the correct response headers.

CORS has several layers. Simply allowing an origin domain only works for a subset of request methods and request headers. Some request methods and headers force a preflight request as a further means of protecting data. The actual request is only allowed if the preflight response headers permit it.

CORS is a major pain point for developers. If a request is blocked, it is not easy to understand why, and how to fix it. An understanding of the nature of the blocked request, and of the CORS mechanisms, can easily overcome such issues.

If you enjoyed this post, you might like related ones on this blog:

  • What is the OAuth 2.0 Implicit Grant Type?
  • Combat Side-Channel Attacks with Cross-Origin Read Blocking

Follow us for more great content and updates from our team! You can find us on Twitter, Facebook, subscribe to our YouTube Channel or start the conversation below.

Cross-Origin Resource Sharing (CORS) is a mechanism that browsers and webviews — like the ones powering Capacitor and Cordova — use to restrict HTTP and HTTPS requests made from scripts to resources in a different origin for security reasons, mainly to protect your user’s data and prevent attacks that would compromise your app.

In order to know if an external origin supports CORS, the server has to send some special headers for the browser to allow the requests.

An origin is the combination of the protocol, domain, and port from which your Ionic app or the external resource is served. For example, apps running in Capacitor have capacitor://localhost (iOS) or http://localhost (Android) as their origin.

When the origin where your app is served (e.g. http://localhost:8100 with ionic serve) and the origin of the resource being requested (e.g. https://api.example.com) don’t match, the browser’s Same Origin Policy takes effect and CORS is required for the request to be made.

CORS errors are common in web apps when a cross-origin request is made but the server doesn’t return the required headers in the response (is not CORS-enabled):

note

XMLHttpRequest cannot load https://api.example.com. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8100’ is therefore not allowed access.

Request with preflight

By default, when a web app tries to make a cross-origin request the browser sends a preflight request before the actual request. This preflight request is needed in order to know if the external resource supports CORS and if the actual request can be sent safely, since it may impact user data.

A preflight request is sent by the browser if:

  • The method is:
    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
  • Or if it has a header other than:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Or if it has a Content-Type header other than:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • Or if a ReadableStream or event listeners in XMLHttpRequestUpload are used.

If any of the conditions above are met, a preflight request with the OPTIONS method is sent to the resource URL.

Let’s suppose we are making a POST request to a fictional JSON API at https://api.example.com with a Content-Type of application/json. The preflight request would be like this (some default headers omitted for clarity):

OPTIONS / HTTP/1.1
Host: api.example.com
Origin: http://localhost:8100
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

If the server is CORS enabled, it will parse the Access-Control-Request-* headers and understand that a POST request is trying to be made from http://localhost:8100 with a custom Content-Type.

The server will then respond to this preflight with which origins, methods, and headers are allowed by using the Access-Control-Allow-* headers:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8100
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type

If the returned origin and method don’t match the ones from the actual request, or any of the headers used are not allowed, the request will be blocked by the browser and an error will be shown in the console. Otherwise, the request will be made after the preflight.

In our example, since the API expects JSON, all POST requests will have a Content-Type: application/json header and always be preflighted.

Simple requests

Some requests are always considered safe to send and don’t need a preflight if they meet all of the following conditions:

  • The method is:
    • GET
    • HEAD
    • POST
  • Have only these headers:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • The Content-Type header is:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • No ReadableStream or event listeners in XMLHttpRequestUpload are used.

In our example API, GET requests don’t need to be preflighted because no JSON data is being sent, and so the app doesn’t need to use the Content-Type: application/json header. They will always be simple requests.

Header Value Description
Access-Control-Allow-Origin origin or * Specifies the origin to be allowed, like http://localhost:8100 or * to allow all origins.
Access-Control-Allow-Methods methods Which methods are allowed when accessing the resource: GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH.
Access-Control-Allow-Headers headers Used in response to a preflight request to indicate which headers can be used when making the actual request, aside from the simple headers, which are always allowed.
Access-Control-Allow-Credentials true or false Whether or not the request can be made with credentials.
Access-Control-Expose-Headers headers Specifies the headers that the browser is allowed to access.
Access-Control-Max-Age seconds Indicates how long the results of a preflight request can be cached.

The browser automatically sends the appropriate headers for CORS in every request to the server, including the preflight requests. Please note that the headers below are for reference only, and should not be set in your app code (the browser will ignore them).

All Requests

Header Value Description
Origin origin Indicates the origin of the request.

Preflight Requests

Header Value Description
Access-Control-Request-Method method Used to let the server know what method will be used when the actual request is made.
Access-Control-Request-Headers headers Used to let the server know what non-simple headers will be used when the actual request is made.

A. Enabling CORS in a server you control

The correct and easiest solution is to enable CORS by returning the right response headers from the web server or backend and responding to preflight requests, as it allows to keep using XMLHttpRequest, fetch, or abstractions like HttpClient in Angular.

Ionic apps may be run from different origins, but only one origin can be specified in the Access-Control-Allow-Origin header. Therefore we recommend checking the value of the Origin header from the request and reflecting it in the Access-Control-Allow-Origin header in the response.

Please note that all of the Access-Control-Allow-* headers have to be sent from the server, and don’t belong in your app code.

Here are some of the origins your Ionic app may be served from:

Capacitor

Platform Origin
iOS capacitor://localhost
Android http://localhost

Replace localhost with your own hostname if you have changed the default in the Capacitor config.

Ionic WebView 3.x plugin on Cordova

Platform Origin
iOS ionic://localhost
Android http://localhost

Replace localhost with your own hostname if you have changed the default in the plugin config.

Ionic WebView 2.x plugin on Cordova

Platform Origin
iOS http://localhost:8080
Android http://localhost:8080

Replace port 8080 with your own if you have changed the default in the plugin config.

Local development in the browser

Command Origin
ionic serve http://localhost:8100 or http://YOUR_MACHINE_IP:8100
npm run start or ng serve http://localhost:4200 for Ionic Angular apps.

Port numbers can be higher if you are serving multiple apps at the same time.

Allowing any origin with Access-Control-Allow-Origin: * is guaranteed to work in all scenarios but may have security implications — like some CSRF attacks — depending on how the server controls access to resources and use sessions and cookies.

For more information on how to enable CORS in different web and app servers, please check enable-cors.org

CORS can be easily enabled in Express/Connect apps with the cors middleware:

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

const allowedOrigins = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:8080',
'http://localhost:8100',
];

// Reflect the origin if it's in the allowed list or not defined (cURL, Postman, etc.)
const corsOptions = {
origin: (origin, callback) => {
if (allowedOrigins.includes(origin) || !origin) {
callback(null, true);
} else {
callback(new Error('Origin not allowed by CORS'));
}
},
};

// Enable preflight requests for all routes
app.options('*', cors(corsOptions));

app.get('/', cors(corsOptions), (req, res, next) => {
res.json({ message: 'This route is CORS-enabled for an allowed origin.' });
});

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

B. Working around CORS in a server you can’t control

Don’t leak your keys!

If you are trying to connect to a 3rd-party API, first check in its documentation that is safe to use it directly from the app (client-side) and that it won’t leak any secret/private keys or credentials, as it’s easy to see them in clear text in Javascript code. Many APIs don’t support CORS on purpose, in order to force developers to use them in the server and protect important information or keys.

1. Native-only apps (iOS/Android)

Use the HTTP plugin from Ionic Native to make the requests natively from outside the webview. Please note that this plugin doesn’t work in the browser, so the development and testing of the app must always be done in a device or simulator going forward.

Usage in Ionic Angular 4
import { Component } from '@angular/core';
import { HTTP } from '@awesome-cordova-plugins/http/ngx';

@Component({
selector: 'app-home',
templateUrl: './home.page.html',
styleUrls: ['./home.page.scss'],
})
export class HomePage {
constructor(private http: HTTP) {}

async getData() {
try {
const url = 'https://api.example.com';
const params = {};
const headers = {};

const response = await this.http.get(url, params, headers);

console.log(response.status);
console.log(JSON.parse(response.data)); // JSON data returned by server
console.log(response.headers);
} catch (error) {
console.error(error.status);
console.error(error.error); // Error message as string
console.error(error.headers);
}
}
}

2. Native + PWAs

Send the requests through an HTTP/HTTPS proxy that bypasses them to the external resources and adds the necessary CORS headers to the responses. This proxy must be trusted or under your control, as it will be intercepting most traffic made by the app.

Also, keep in mind that the browser or webview will not receive the original HTTPS certificates but the one being sent from the proxy if it’s provided. URLs may need to be rewritten in your code in order to use the proxy.

Check cors-anywhere for a Node.js CORS proxy that can be deployed in your own server. Using free hosted CORS proxies in production is not recommended.

C. Disabling CORS or browser web security

Please be aware that CORS exists for a reason (security of user data and to prevent attacks against your app). It’s not possible or advisable to try to disable CORS.

Older webviews like UIWebView on iOS don’t enforce CORS but are deprecated and are very likely to disappear soon. Modern webviews like iOS WKWebView or Android WebView (both used by Capacitor) do enforce CORS and provide huge security and performance improvements.

If you are developing a PWA or testing in the browser, using the --disable-web-security flag in Google Chrome or an extension to disable CORS is a really bad idea. You will be exposed to all kind of attacks, you can’t ask your users to take the risk, and your app won’t work once in production.

Sources
  • CORS Errors in Ionic Apps
  • MDN

What is CORS?

CORS is an acronym. It stands for ‘Cross-Origin Resource Sharing’.

If you are involved in creating, configuring or maintaining a website then you may need to know about CORS.

Web browsers have a security feature known as the same-origin policy. This policy prevents code on one website from
talking to a different website unless certain conditions are met.

Roughly speaking you can think of ‘origin’ as meaning ‘website’ or ‘server’. So when we talk about ‘same-origin’ we mean
‘same-website’ or ‘same-server’. To find out more about exactly how origin is defined see
What does ‘origin’ mean?.

In short, CORS is a way to turn off this security feature to allow AJAX requests to a different site.

The site making the AJAX request can’t just turn off security. That would be back-to-front. As far as the browser is
concerned that site might be malicious and can’t be trusted. It is the server receiving the AJAX request that decides
whether to allow CORS.

Why am I getting a CORS error when I’m not even using CORS?

If your website has attempted to make an HTTP request to a different site then the browser will try to use CORS. You
don’t have a choice, it happens automatically. If the target server doesn’t have CORS enabled then the request will fail
and the browser will log a CORS error to the console.

Using the same domain with two different port numbers is not sufficient to avoid CORS. The port number is considered
to be part of the origin when the browser decides whether or not to use CORS.

What does ‘origin’ mean?

Consider the following URL:

http://www.example.com:3456/path?query=text

The origin for this URL would be http://www.example.com:3456. It includes the scheme, hostname and port.

http://www.example.com:3456
<scheme> :// <hostname> [ : <port> ]

The port may be omitted if it is the default port for the scheme, so 80 for http or 443 for https.

When the browser makes a CORS request it will include the origin for the current page in the Origin request header.

In JavaScript you can access the current page’s origin using location.origin.

How does CORS work?

Let’s start with the basics.

There are two questions that the browser needs to answer:

  1. Are you allowed to make the request at all?
  2. Are you allowed to access the response?

There are some requests that are considered simple enough and safe enough that the first stage is skipped. We’ll come
back to that later.

For the second question, the browser assumes by default that you can’t access the response for a cross-origin request.
That includes the response body, the response headers and the status code.

In addition to the usual request headers the browser will also include an Origin header, containing the origin of the
page making the request. Note this is added automatically by the browser, you can’t add it yourself.

The server then needs to grant permission for the response to be exposed to the client-side code. It does this by
including the response header Access-Control-Allow-Origin. The value of this header must be identical to the value of
the Origin header it received. It can also use the special value * to allow any origin. Any other value will cause
the CORS check to fail in the browser.

It is really important to understand that Access-Control-Allow-Origin is a response header sent by the server. It is
NOT a request header. It is a very common mistake to try to set this header on the request, which won’t help at
all.

// Don't copy this code, it is wrong!!!
axios.get(url, {
  headers: {
    // This is in the wrong place. It needs to be on the server.
    'Access-Control-Allow-Origin': '*'
  }
})

In addition, only a limited subset of the response headers will be exposed by default. See
Why can’t I access the response headers in my JavaScript code? for more information.

If a request is not deemed ‘simple’ and ‘safe’ then it must first undergo a preflight check to determine whether to
allow the request at all. See What is a preflight request? for more information.

Why am I seeing an OPTIONS request instead of the GET/POST/etc. request I wanted?

What is a preflight request?

The term is a reference to the preflight checks carried out by pilots.

It is a request generated automatically by the web browser. It is used to check whether the server is willing to allow
the original request.

Before CORS existed you couldn’t make AJAX requests to other servers. However, you could make requests by other means,
such as by submitting a form or including a <script src="..."> in your page.

Those alternative means of making requests had some limitations. e.g.:

  • You could only make GET and POST requests.
  • There was no way to set custom request headers.
  • A POST request could only have a content-type of application/x-www-form-urlencoded, multipart/form-data or
    text/plain.

When CORS was introduced it was important to ensure that no new security holes were opened up. If an AJAX request
tried to do something beyond the limitations listed above then it might expose a new security vulnerability in the
target server.

To get around this problem the browser first checks with the target server to see whether it will allow the main
request. This check takes the form of an HTTP OPTIONS request. Here OPTIONS refers to the request method, it’s one
of the alternatives to GET and POST. The request headers of the OPTIONS request describe the main request to the
server and then the server responds via the response headers.

An OPTIONS request was chosen for this purpose because most web servers already implemented some form of OPTIONS
request handling and such requests should always be harmless to the server.

You don’t have direct control over the preflight request, it’s made automatically by the browser. It will look something
like this:

OPTIONS /api-path HTTP/1.1
Origin: http://localhost:8080
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

Other headers will be included but they aren’t important here. Breaking this down line-by-line:

  1. The path /api-path shown here is just an example and will match the URL path of the original request.
  2. The Origin header will match the origin of the page making the request. See What does ‘origin’ mean?.
    This is exactly the same as the Origin header that will be included on the main request.
  3. The header Access-Control-Request-Method indicates the request method of the main request.
  4. The header Access-Control-Request-Headers is a comma-separated list of custom headers that were set on the request.
    Headers set by the browser aren’t included, so any headers listed here were set somewhere in the code that attempted
    the original request. It doesn’t tell us the values of those headers, just that they were set to something other than
    their default values.

To allow this request, the server response should look like this:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: content-type

Again, breaking this down line-by-line:

  1. The status code must be in the range 200299 for a preflight request to succeed.
  2. Just like for the main request, Access-Control-Allow-Origin must either match the Origin or be *.
  3. The response header Access-Control-Allow-Methods is a comma-separated list of allowed request methods. GET,
    POST and HEAD requests are always allowed, even if they aren’t included in the list.
  4. Access-Control-Allow-Headers is also a comma-separated list. For the request to be allowed it must include all of
    the headers that were listed in Access-Control-Request-Headers.

If any of the CORS response headers are dynamically generated based on the request headers then those request headers
should be listed in the Vary response header. e.g. Vary: Origin. This helps to avoid caching problems.

No request body will be sent for a preflight request and the response body will be ignored.

My request works fine in Postman/cURL/etc.. Why do I get a CORS error in the browser?

The same-origin policy is a security feature built into web browsers. It doesn’t apply if you make requests using
tools such as Postman or cURL. Concepts such as ‘same-origin’ and ‘cross-origin’ don’t even make sense in that context.
Same origin as what? There is no current page making the request, so there is no origin to compare.

Any CORS errors you see in the browser console are not generated by the server. At the network level the request
probably succeeded. The browser may well have received exactly the same response that you see with Postman or cURL. It’s
the browser that performs the CORS checks and blocks access to the response in JavaScript if those checks fail.

The biggest difference between a browser and tools like Postman is the preflight OPTIONS request. As those tools don’t
use CORS they aren’t going to send a preflight request automatically. However, a preflight is just an HTTP request so it
is possible to send it manually instead:

  • How can I use cURL to test a preflight request?
  • Can Postman send a preflight request?

Within the browser it’s usually pretty clear from the error message whether it’s the preflight request that’s failing.

How can I use cURL to test a preflight request?

In Chrome or Firefox you should be able to see the preflight OPTIONS request in the Network tab of the developer
tools. Right-clicking on the request should present an option to Copy as cURL.

To make the copied request useful you will also need to add the -I option. This includes the response headers in the
output.

Most of the request headers included by a browser aren’t necessary from a CORS perspective. Servers will usually ignore
those other headers when responding to a preflight request. Generally a much simpler cURL request will suffice:

curl 
  -I 
  -X OPTIONS 
  -H "Origin: http://localhost:8080" 
  -H "Access-Control-Request-Method: POST" 
  -H "Access-Control-Request-Headers: Content-Type" 
  http://localhost:3000/api

Breaking this down:

  1. The symbols are used to break the command over multiple lines. You can remove them and put the whole thing on one
    line if you prefer.
  2. As mentioned previously the -I will output the response headers.
  3. The -X is used to set the request method. For a preflight this must be OPTIONS.
  4. -H adds a request header.
  5. The values of the three headers will need changing to match the request you are trying to make. You should omit
    Access-Control-Request-Headers if there are no custom headers.
  6. The final line is the URL of the target server. Again this is something you will need to change to match the request
    you are trying to make.

Can Postman send a preflight request?

A preflight request is just an HTTP request, so it can be sent using Postman.

To send the request manually you’ll need to select OPTIONS for the request method and then set suitable values for the
headers Origin, Access-Control-Request-Method and Access-Control-Request-Headers.

If you want a preflight request to be generated automatically then you could use Postman’s Pre-request Script feature
instead. The code below is an example of how to generate a preflight request for another request. You should be able to
drop this code straight into the Pre-request Script tab for your target request:

(function () {
  const request = pm.request
  const url = request.url.toString()
  const requestMethod = request.method
  const headers = request.headers.toObject()
  const origin = headers.origin

  if (!origin) {
    console.log(`The request must have an Origin header to attempt a preflight`)
    return
  }

  delete headers.origin

  const requestHeaders = Object.keys(headers).join(', ')

  if (!['GET', 'HEAD', 'POST'].includes(requestMethod)) {
    console.log(`The request uses ${requestMethod}, so a preflight will be required`)
  } else if (requestHeaders) {
    console.log(`The request has custom headers, so a preflight will be required: ${requestHeaders}`)
  } else {
    console.log(`A preflight may not be required for this request but we'll attempt it anyway`)
  }

  const preflightHeaders = {
    Origin: origin,
    'Access-Control-Request-Method': requestMethod
  }

  if (requestHeaders) {
    preflightHeaders['Access-Control-Request-Headers'] = requestHeaders
  }

  pm.sendRequest({
    url,
    method: 'OPTIONS',
    header: preflightHeaders
  }, (err, response) => {
    if (err) {
      console.log('Error:', err)
      return
    }

    console.log(`Preflight response has status code ${response.code}`)
    console.log(`Relevant preflight response headers:`)

    const corsHeaders = [
      'access-control-allow-origin',
      'access-control-allow-methods',
      'access-control-allow-headers',
      'access-control-allow-credentials',
      'access-control-max-age'
    ]

    response.headers.each(header => {
      if (corsHeaders.includes(header.key.toLowerCase())) {
        console.log(`- ${header}`)
      }
    })
  })
})()

This code requires the original request to have an Origin header set. You can see the results of the preflight in the
Postman Console. The code makes no attempt to perform a CORS check on the response headers, you’ll need to verify the
response yourself.

Why am I seeing a preflight OPTIONS request when I’m not setting any custom headers?

The first step to debugging an unexpected preflight request is to check the request headers on the preflight request.
The headers Access-Control-Request-Method and Access-Control-Request-Headers should clarify why the browser thinks a
preflight in required.

First check Access-Control-Request-Method. If it’s set to GET, HEAD or POST then that isn’t the problem. Those 3
request methods are considered safe and won’t trigger a preflight request. For any other values, e.g. PUT or DELETE,
that’s enough to trigger a preflight request.

The other header to check is Access-Control-Request-Headers. This will provide a comma-separated list of header names.
These are the names of the request headers that triggered the preflight request. It won’t tell you the values, just
names. If the preflight succeeds then you’ll be able to find the values on the main request.

Often once you see the names of the headers it’s obvious where they are coming from. But not always. A quick search
through your code can help but sometimes even that doesn’t reveal the source of the rogue headers.

If you’re using a library it may be automatically setting headers for you. Some common examples includes:

  • X-Requested-With being set to XMLHttpRequest.
  • Content-Type. The values application/x-www-form-urlencoded, multipart/form-data and text/plain won’t trigger a
    preflight request but any other value will. Most AJAX libraries will attempt to set the Content-Type header for
    requests that have a body, such as POST requests. Some libraries will automatically set the Content-Type to
    application/json when the body contains JSON data. For a cross-origin request that will trigger a preflight.
  • Authorization. If you’re providing options such as username and password then it is likely that the library will
    be converting them to an Authorization header.

If you still can’t find where the custom header is coming from then you may need to step through the code. Put a
breakpoint just before your request and step into the library code to see exactly what is going on.

How can I avoid the preflight OPTIONS request?

Caching

If you want to cut down the number of preflight requests you should consider using caching. Preflight caching is
controlled using the response header Access-Control-Max-Age.

Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 3600
Vary: Origin

The Vary header is used to identify which request headers were used to generate the response. The cached response
should only be used if the values of those headers are unchanged.

Most browsers will limit the caching time for preflight requests to a few hours, depending on the browser.

The requests must share the same URL to get any benefit from caching. If your URLs are all slightly different, possibly
because they include resource ids, then you may want to consider moving those differences into the request body instead.

Completely avoiding a preflight

Avoiding preflight requests is primarily a server-side problem and may require significant architectural changes
depending on how your application is structured. Ultimately the server dictates the form of the requests and if those
requests require a preflight then there’s nothing the client-side code can do to avoid it.

First you’ll need to limit requests to GET, HEAD or POST. If you’re using other request methods such as PUT or
DELETE then you’ll need to change them to POST.

Some custom request headers are allowed without triggering a preflight, though generally they’re not the most useful
headers. If you have an existing request that is triggering a preflight then the simplest way to confirm which headers
are to blame is to check the value of Access-Control-Request-Headers. See Why am I seeing a preflight OPTIONS request when I’m not setting any custom headers? for
more information.

Most custom headers can either be removed or moved into the request body instead.

To avoid a preflight request the Content-Type header must be one of application/x-www-form-urlencoded,
multipart/form-data or text/plain. If the body of your POST requests is not in one of these three formats then
you’ll need to lie in order to avoid a preflight. If you’re using a third-party library to parse the content then it may
need reconfiguring to cope with the misleading Content-Type header. This can be difficult if your server supports
multiple types of content. You could use different paths for different formats, or use a URL query parameter to pass the
true Content-Type. Either way you may find yourself needing to be inventive to get the parser to understand which
requests it should parse.

The Authorization header can also be problematic as it is commonly used by third-party libraries. Whether this is a
solvable problem will depend on the library you’re using.

Why am I seeing ‘405 — Method Not Allowed’?

There are a few reasons why you might be seeing this error.

A 405 status code usually indicates that a request is using the wrong request method. e.g. Using POST when it should
be GET. That applies to any request, not just CORS requests. Before you do anything else it is worth quickly checking
which request method the server is expecting and making sure that you’ve used the correct one when making the request.
Check the URL too, make sure you haven’t copied it from somewhere else without updating it.

From a CORS perspective the most likely cause of problems is the preflight OPTIONS request. That’s usually pretty easy
to identify as you’ll see an error message in the browser console telling you that the preflight request has failed.
Typically the problem is simply that the server hasn’t been configured to support a preflight OPTIONS request. That
may be a server bug, or it may be that you’re triggering an unnecessary preflight. See
What is a preflight request?. If you have control over the server then also consult the documentation for your
server-side stack to check what is required to enable a CORS preflight request.

Next, use the Network tab of the developer tools in your browser to check exactly which request is failing. Pay
particularly close attention to the request method, don’t just assume it’s what you wrote in your code. Most browsers
allow the columns shown in the Network tab to be configured, so add Method if it isn’t already showing.

If you have the option of checking the server logs then that may also help to provide important clues.

HTTP redirects are also a common source of 405 errors, though not specifically related to CORS. This includes
redirecting from http to https or redirects to add or remove trailing slashes. Depending on how this is implemented
it can cause the request to change method to GET, causing a 405.

What is withCredentials? How do I enable it?

withCredentials is a flag that can be set on XMLHttpRequest for cross-origin requests. It is usually used to enable
cookies. It is also required to enable browser-based HTTP authentication, though that can also be implemented manually.

Note that withCredentials is only required for specific forms of ‘credentials’. Just because you’re using some form of
authentication with credentials doesn’t necessarily mean that you need to enable withCredentials.

If you’re using XMLHttpRequest directly it would be set as follows:

const httpRequest = new XMLHttpRequest();
httpRequest.withCredentials = true;

It can be set at any point prior to calling httpRequest.send().

For making requests with fetch the equivalent of withCredentials is setting credentials to 'include':

fetch(url, {
  credentials: 'include'
});

With jQuery the withCredentials flag can be set using:

jQuery.ajax({
  // ...other settings...

  xhrFields: {
    withCredentials: true
  }
});

For axios:

axios.post(url, body, {
  withCredentials: true
});

Note that withCredentials is not a header and should be included directly in the request options.

The use of withCredentials is not a factor in determining whether a preflight request is required. Even though
withCredentials can lead to the automatic inclusion of Cookie and Authorization headers on the request they are
not considered to be custom headers for the purposes of the preflight check.

When using withCredentials the server response must include the header Access-Control-Allow-Credentials: true,
otherwise the request will fail the CORS check. This header must also be included on the preflight response, if there is
one.

The use of * values in CORS response headers is also prohibited when using withCredentials. For
Access-Control-Allow-Origin the value of the Origin request header should be used instead but only after it has been
checked to ensure the origin can be trusted. See What are the security implications of CORS? for more information about why
this matters.

While Safari does support withCredentials it tends to have a stricter security policy than other browsers. If you need
to use withCredentials then you should test in Safari sooner rather than later to check whether what you’re trying to
do is actually allowed. For example, to set cookies you will need both origins to share the same domain.

What happens when a CORS request fails?

The most obvious sign that a request has failed due to CORS is an error message in the browser console. This will
usually give a clear indication of why it failed. For more information about CORS error messages in Chrome see our
list of CORS error messages.

It’s important to appreciate that CORS error messages come from the browser, not from the server. The browser applies
the CORS checks to the response headers after a response is successfully received.

A notable exception is the message Reason: CORS request did not succeed, which is shown in Firefox. If you just see
that message then it is possible that the request failed at the network level. For example, you will see that message if
the target server couldn’t be contacted. The message is somewhat misleading as CORS is not really relevant to the
problem. The equivalent message in Chrome doesn’t mention CORS and is the same message that would be shown for a
same-origin request.

The Network section of the browser’s developer tools won’t tell you directly whether the request failed a CORS check,
though that is usually the easiest way to check the relevant headers.

If a CORS preflight OPTIONS request fails then the main request won’t occur.

In some browsers the preflight request won’t be shown separately in the developer tools. Any console errors should make
it clear whether it was the preflight request that failed.

In your JavaScript code all CORS failures will be presented the same way. You’ll see a status code of 0 and you won’t
be able to access the response headers or the response body. You won’t have access to any helpful error messages as
exposing those error messages is regarded as a security risk.

In some cases cookies will be set even though the request failed a CORS check. However, unless the cookie domain is a
match for the current page you won’t be able to access those cookies via document.cookie.

A preflight OPTIONS request is expected to return a status code in the range 200 to 299, otherwise it will fail.
However, the main CORS request can use status codes just like any other AJAX request. A status code that indicates an
error will not cause the CORS checks to fail and, if the CORS checks pass, that status code will be accessible in your
JavaScript code.

Why is CORS so complicated?

How complicated you find CORS depends on your starting point.

If you’re expecting cross-origin requests to be exactly the same as same-origin requests then it will definitely seem
complicated. Unfortunately, the same-origin policy is very much needed, it is not just browser makers worrying about
nothing.

It’s quite likely that your first encounter with CORS was an error message in your browser’s console. Blissfully
ignorant of its significance you probably expected that there’d be a nice, easy fix.

The good news is that CORS does provide a solution. Prior to CORS being introduced you’d have been in real trouble,
fumbling with JSON-P for a bit before giving up and throwing your whole project in the bin.

The bad news is that CORS had to be shoe-horned into the existing design of the web without breaking anything or
introducing significant new security problems.

If you aren’t really familiar with the inner workings of HTTP then CORS may be dragging you into unknown territory. HTTP
headers and OPTIONS requests nicely solve the problem but if you haven’t come across the basic concepts before then
they make the CORS learning curve seem a lot steeper than it actually is.

There are several factors that contributed extra complexity to the design of CORS:

  • Security must be backwards compatible with servers that don’t understand CORS.
  • Special cases have been added to make simple scenarios easier. However, if you want to understand the full picture
    then those special cases are just extra stuff to learn.
  • There was a desire to support browser caching, especially for the preflight request. This is one reason why the
    preflight response requires multiple headers and not just a simple yes-or-no header.

If you’re new to CORS and getting a bit overwhelmed then it may help to reset your expectations. Be patient and allow
yourself some time to find out how CORS works. If you try to rush it you’ll just get lost.

Remember that CORS exists because of very real security concerns. You need to make an informed decision about exactly
how much of that security you want to turn off.

CORS is annoying. Why can’t I turn it off?

Some browsers have command-line options or similar settings to turn off the cross-origin security restrictions. The
details are not included here because this is not something you should be doing, even during development. If you do need
a temporary workaround for development see What are the alternatives to CORS?.

If the web were to be redesigned from scratch it might look very different. But that isn’t going to happen. Instead
browsers have to do the best they can with the web we have today.

Web browsers have to protect their users from malicious sites. You may know that your site isn’t malicious but a
browser doesn’t know that.

For a cross-origin request the browser is trying to protect the other server from your site. It assumes that your site
could be malicious, so it wouldn’t make sense to allow your site to disabled the security protection.

Perhaps you control both servers. As far as you’re concerned they’re both part of the same site. But the browser doesn’t
know that, it just sees two different origins (servers) and has to treat them as totally separate.

Before CORS existed the same-origin policy just blocked cross-origin AJAX requests. Now that was really annoying. At
least with CORS the server can choose to allow the request.

What are the alternatives to CORS?

Before CORS existed there was JSON-P. Now that browser support for CORS is universal there’s no good reason to use
JSON-P instead.

If you just need a solution locally during development then you could try using a browser extension. Typically these
extensions will intercept the server response and inject CORS headers. This may be sufficient in some cases but it’s
not ideal as the requests are still made cross-origin, leading to potential problems, e.g. with cookies.

If you’re trying to contact a publicly accessible server then you could try using CORS Anywhere,
cors-anywhere.herokuapp.com. It acts as a middle-man, adding in the
required headers to get CORS to work. For experiments or demos this might be a satisfactory solution but it’s not a good
idea to use it for a production application.

The other alternative is to use a reverse proxy to channel all requests through a single server. This may be a viable
solution both during development and in production. Cross-origin restrictions don’t apply if the requests all target the
same origin as the current page. The server needs to be configured to pass on relevant requests to the other server.
When the response shows up, it passes that back to the browser. As far as the browser is concerned it’s just talking to
one site.

This might be a good solution but there are some drawbacks to consider:

  • Many hosting solutions will not allow you to configure proxying.
  • As the AJAX requests are now going through your server the load on that server will increase. The network
    infrastructure between the two servers will also have to cope with the extra demand.
  • The total request time will increase.
  • If you are proxying someone else’s site you might be violating the terms and conditions of that site.
  • The other site will see all requests as having come from your IP address. If you make too many requests you may be
    throttled or even blocked.
  • If sensitive data is being transferred then you are now responsible for protecting that data while it passes through
    your server.

I can’t change the server and it isn’t using CORS. What else can I do?

Does using HTTPS have any effect on CORS?

Yes.

Requests from HTTPS

If the page making the request is using HTTPS then the target URL must also be HTTPS. Trying to access resources using
http from an https page is known as mixed content and will be blocked the browser. It won’t even attempt the
request and there should be a clear error message in the console.

Most browsers relax the rules for CORS requests to localhost and 127.0.0.1, so it is possible to make requests
locally using http. This is considered lower risk as the request never actually leaves the user’s device. Safari
currently doesn’t implement this special case and will block any request from https to http.

Requests to HTTPS

If the requesting page has a scheme of http then it can make CORS requests to both http and https URLs.

Invalid SSL certificates, especially self-signed certificates, are a common problem when using CORS. These can result in
requests failing for no apparent reason. For a same-origin request the certificate of the requesting page will be the
same as the requested URL, so any problems with the certificate will have been handled as soon as the page was opened.
For a CORS request the certificate won’t be checked until the request is made, so it fails quietly in the background.

Usually the easiest way to check the certificate is to go directly to the URL in a browser. Even though this won’t be
using CORS it will still be sufficient to check that the certificate is valid. With a self-signed certificate this will
allow you to add an exception so that the certificate will be trusted in future.

Cookies

As of Chrome 80, cookies with SameSite=None must also set the Secure directive. So if you need cross-domain
cookies you’ll need to use HTTPS.

Note that a cookie’s domain is not quite the same thing as origin, so it is possible to have cross-origin cookies
without HTTPS if the domains match.

For more information see Why aren’t my cookies working with CORS?.

Does CORS work with localhost?

Yes. From a CORS perspective localhost and 127.0.0.1 are almost the same as any other domain or IP address.

The server you are attempting to contact may choose to allow requests only from specific origins. CORS itself doesn’t
make a special case for localhost but a server can single out localhost if it so wishes.

Typically localhost is only used during development. This can lead to a perception that localhost is somehow the
cause of a CORS problem. In reality it’s a case of correlation not implying causation. Some more likely causes are:

  1. A bug in the code or CORS configuration.
  2. Caching, making a problem appear to stick around even after it is fixed.
  3. An invalid or self-signed SSL certificate.
  4. Not binding the target server to localhost. Try contacting the server directly to be sure it is accessible
    via localhost.
  5. A browser plugin, debugging proxy or some other piece of development trickery.

Far from localhost having tighter CORS restrictions, in some cases it actually has weaker restrictions (see HTTPS
and Cookies below). This can cause problems in production that didn’t occur during development. To avoid such problems
you may want to consider adding aliases to your hosts file so that you can use URLs during development that are a
closer match to the production URLs.

HTTPS

There is a special case in some browsers for mixed content. If an https page attempts a request to an http page then
this is usually blocked. However, if the target page is using localhost then a CORS request is attempted.

See Does using HTTPS have any effect on CORS? for more information but, in short, if you want to use https for the
requesting page you’ll also need to use https for the target server.

Cookies

Consider this relatively common scenario.

During development you might be running both servers on localhost. Let’s say the UI is hosted at
http://localhost:8080 with a data server at http://localhost:3000.

You open http://localhost:8080 in your web browser and you’re presented with a login page. You enter your username and
password and the page sends a login request to http://localhost:3000. This returns a cookie using the Set-Cookie
header. The request had withCredentials set to true and the cookie seems to work as expected.

When this site reaches production the UI is hosted from http://www.example.com and the data server is at
http://api.example.com. Suddenly the cookies stop working.

The problem is that cookies are tied to a domain. Working locally both localhost:8080 and localhost:3000 are
considered to have a cookie domain of localhost. So even though it was a cross-origin request, from a cookie
perspective it’s considered ‘same site’.

For the production site the cookie’s domain would default to api.example.com, which is not a match for
www.example.com. As they are both subdomains of example.com this can easily be fixed by explicitly setting the
Domain directive on the cookie. However, the key point to note is that production behaves differently from the
development environment.

If you want to know more about working with cookies and CORS see Why aren’t my cookies working with CORS?.

I’ve tried to implement CORS but it isn’t working. What should I do next?

First make sure you’ve understood how CORS works. If you aren’t clear on that then you’ll waste a lot of time trying
to debug any problems.

There are a lot of third-party libraries available that implement CORS for various different types of server. These
can save you some of the work but it is still important to understand how the underlying CORS mechanism works or
you’ll likely run into problems.

If CORS has failed you’ll probably see an error message in your browser’s console. If you don’t see an error message
then check that you don’t have any filters turned on that might be hiding the message.

If you’re seeing a CORS-related error message but you aren’t sure what it means then try consulting our
list of CORS error messages.

The next thing to try is the Network tab of the developer tools. Find the request that isn’t working and
check exactly what is being sent each way.

If you’re seeing an OPTIONS request that you weren’t expecting then see Why am I seeing an OPTIONS request instead of the GET/POST/etc. request I wanted?.

If you’re seeing the warning ‘Provisional headers are shown’ then see Why can’t I access the response headers in Chrome’s developer tools?.

If you think all the headers look correct then you can check them using our CORS header checker.

I’ve configured my server to include CORS headers but they still aren’t showing up. Why?

First check for any error messages in the server logs.

If that doesn’t help, here are some common problems to check:

  • Have you saved the config file?
  • Is the config file in the right place?
  • If you have multiple servers, have you changed the correct one?
  • The server may need restarting. Make sure you restart the correct server.
  • CORS may be configured for some requests but not the request you’re attempting.
  • Is the failing request a preflight OPTIONS request? Does your configuration handle that?
  • Could a proxy or intermediary server be removing the headers?
  • Check for typos in your config. e.g. Spelling (allow / allowed), plural vs singular (headers / header),
    case-sensitivity.
  • If you’re using an online tutorial, is it for a compatible version of the server and/or CORS plugin that you’re using?

One trick that can be useful is to try changing something unrelated in the config file, see whether that works. The
idea is to confirm that the latest version of the config file is definitely being used. Even deliberately breaking the
config file so that the server won’t start is enough to confirm that your changes are having an effect.

Why aren’t my cookies working with CORS?

There are several questions in one here:

  1. How can cookies be set using the Set-Cookie response header using CORS?
  2. Why can’t I see my cookies in the developer tools?. This is so common it gets a separate question in the FAQ.
  3. Can CORS cookies be accessed from JavaScript using document.cookie?
  4. I’ve set the cookies but they aren’t being included on subsequent CORS requests. Why?

There’s a complication that warrants mentioning up front. Cookies are bound to a domain and path, not an origin.
So we’ve actually got two slightly different concepts of ‘same site’ to juggle. Fun times.

Setting a cookie with Set-Cookie

Even if you aren’t using CORS a cookie can disappear because one of its directives is incorrectly set. As that isn’t
relevant to CORS we aren’t going to go into detail here but you should check that directives such as Expires,
Max-Age, Domain, Secure, etc. aren’t set to inappropriate values.

Now let’s consider the case where the domains/origins are totally different. We’ll come back to the muddy waters in the
middle later.

If you’re using XMLHttpRequest to make a CORS request then you’ll need to set the withCredentials flag to true.
For fetch the equivalent setting is credentials: 'include'. For more information on that see
What is withCredentials? How do I enable it?.

Once you’ve set this flag you’ll likely see a number of errors and warnings in your browser’s console. What follows
below is mostly just an explanation of how to fix those errors.

On the server, as well as returning the Set-Cookie and Access-Control-Allow-Origin headers, you’ll also need to
return an extra CORS header to allow credentials:

Access-Control-Allow-Credentials: true

If the request requires a preflight then that must also include this header.

Using credentials disables the * wildcard for the other CORS response headers, so if you’re using that you’ll need to
replace it with explicit values. The most common problems are with Access-Control-Allow-Origin, which will need to
return the exact value of the Origin request header instead of *.

Then there’s the SameSite directive of Set-Cookie to consider. For cross-domain requests it needs to be set to
None or the cookie will be ignored. Note that cross-domain isn’t quite the same thing as cross-origin, we’ll
elaborate on that distinction shortly. In most browsers None is the default value but as of Chrome 80 this is
changing, www.chromium.org.

From February 2020 Chrome will be transitioning the default value to Lax, so SameSite=None will need to be set
explicitly.

As part of the same transition, Chrome will also require that cookies using SameSite=None also use the Secure
directive, which requires https. So if you want to use cross-domain cookies you’re going to need https.

Putting all those headers together we get something like this:

Access-Control-Allow-Origin: example.com
Access-Control-Allow-Credentials: true
Set-Cookie: my-cookie=value; SameSite=None; Secure

Even if you do all this the cookie still won’t be set in Safari, which has tighter security restrictions than other
browsers. There is a workaround though…

At this point we need to go back to those muddy waters around origins and cookie domains.

Let’s consider a website running at http://localhost:8080 making AJAX requests to http://localhost:3000. The ports
don’t match so they have different origins. We’re in CORS territory.

However, a cookie domain is not the same thing as an origin. Cookies for both of these servers will have a domain of
localhost. The port is ignored. So from a cookie-domain perspective they count as the same site.

Keep in mind that cookies were introduced to the web a long time ago. If they were introduced from scratch today they
would likely be designed very differently.

So continuing with our example website running at http://localhost:8080, it has the same cookie domain as
http://localhost:3000. They share a cookie jar. In JavaScript code the cookies will be accessible via
document.cookie, no matter which of the two servers set a particular cookie. The SameSite directive can be set to
None, Lax or Strict — it doesn’t matter because from a cookie perspective they count as the same site. You’ll
still need to use Secure if you want SameSite=None with newer browsers but if both of your servers share a domain
you probably don’t want to be using SameSite=None anyway.

Using a shared cookie domain isn’t limited to localhost but it is a little more complicated once subdomains get
involved. If you have a website running at http://www.example.com making AJAX requests to http://api.example.com
then they won’t share cookies by default. However, a cookie can be shared by explicitly setting the Domain to
example.com:

Set-Cookie: my-cookie=value; Domain=example.com

Even Safari will allow cross-origin cookies to be set so long as they share a cookie domain.

If you’ve read all that and still can’t figure out why your cookies aren’t being set, try using our
CORS header checker to check that you’re setting the response headers correctly. Also take a look at
Why can’t I see my cookies in the developer tools?.

Accessing a cookie with document.cookie

A cookie set via a CORS request can be accessed in JavaScript via document.cookie but only if the cookie’s domain
is a match for the current page. Whether a CORS request was used to set the cookie is not actually relevant.

Including a cookie on a CORS request

Let’s assume that you’ve successfully managed to set a cookie for the correct domain. How do you include that on
subsequent CORS requests to that domain?

The process is quite similar to setting a cookie using CORS. The withCredentials flag must be set to true and the
server will need to return Access-Control-Allow-Credentials: true. As before, wildcards won’t be supported for any
CORS response headers.

Cookies with a SameSite value of Strict or Lax will only be sent if the page domain matches the cookie domain. If
the domains don’t match then SameSite must be None. This is consistent with setting a cookie using CORS so it will
only be a problem if the cookie was set by some other means.

While Safari has tighter restrictions for setting cookies, the rules for including cookies on subsequent requests are
much the same as for other browsers. So while a same-domain request may be required to set the cookie, it can then be
included on a cross-domain request from a different page.

Why can’t I see my cookies in the developer tools?

This is a common misunderstanding.

The developer tools will only show cookies for the current page. Cookies for cross-origin AJAX requests are usually
not regarded as being part of the current page, so they aren’t shown.

Depending on the specifics of your scenario you may see the cookies in the developer tools or you may not. For example,
if you’re running two servers on localhost with different ports then they will share a cookie domain, so the cookies
should show up.

Just because the cookies aren’t shown in the developer tools doesn’t mean that they don’t exist.

To see the cross-origin cookies in the developer tools you’ll need to open another tab with a URL that has the same
domain as the cookie. It doesn’t matter exactly which URL you choose but you should be careful to pick a URL that won’t
change the cookies itself. You’ll also need to open a separate copy of the developer tools for the new tab. You should
then be able to see what cookies are set for that origin.

Alternatively, most browsers provide some mechanism for viewing all cookies. It’s usually hiding somewhere in the
privacy settings. At the time of writing the following URIs will get you to the right place:

  • Chrome: chrome://settings/siteData
  • Firefox: about:preferences#privacy

Of course, the other reason why you may not be able to see the cookies in the developer tools is because no cookies are
being set. See Why aren’t my cookies working with CORS? for more information.

Why can’t I access the response headers in my JavaScript code?

By default, only the following response headers are exposed to JavaScript code for a CORS request:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

These are known as the CORS-safelisted response headers.

The specification was recently changed to add Content-Length to the list of CORS-safelisted response headers. This has
been implemented in some browsers but at the time of writing it still isn’t included in Firefox.

To expose other response headers you need to use Access-Control-Expose-Headers. See
developer.mozilla.org
for more information.

Which CORS response headers go on the preflight response and which go on the main response?

These two headers should be included on both the preflight and the main response:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Credentials

The following headers should only be included on the preflight response:

  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Max-Age

The Access-Control-Expose-Headers header should only be included on the main response, not the preflight.

Most of these response headers are optional, depending on the circumstances. The only header that is always required for
a CORS request to succeed is Access-Control-Allow-Origin. For a preflight request, at least one of
Access-Control-Allow-Methods or Access-Control-Allow-Headers will also be required.

Why can’t I see my request in the Network section of the developer tools?

First and foremost check for console errors. The simplest explanation for a missing request is that there’s a bug in
your code and the request didn’t actually happen.

One particularly noteworthy error shown in Chrome is:

Access to XMLHttpRequest at ‘localhost:8080/api’ from origin ‘http://localhost:3000’ has been blocked by CORS policy:
Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

Interestingly, the same problem in Firefox doesn’t show any error message, the request just fails without explanation.

The source of this problem is that the URI is missing the prefix http:// or https://, so the browser interprets the
localhost: as being the scheme of the URI. More information.

However, assuming you aren’t seeing that particular error…

In most browsers the developer tools must be open before you make the request, otherwise it won’t be recorded.

Check you don’t have any filters turned on which could be hiding your request.

In Safari and older versions of Chrome you won’t be able to see the preflight OPTIONS request as a separate request.

Preflight OPTIONS requests can also be cached, so it’s possible you may not see the request you’re expecting because a
cached response has been used instead. If you have access to the server logs you may be able to confirm what requests
were actually received.

It can be useful to check across multiple browsers. Using a new Incognito/Private window can also help to bypass any
caching problems that might be interfering with your requests.

Why can’t I access the response headers in Chrome’s developer tools?

When you try to access the headers via the Network tab of Chrome’s developer tools you may see the warning:

Provisional headers are shown

Further, the context-menu for the request won’t show the options to Copy request headers or
Copy response headers.

There are several possible causes for this warning, some of which are not directly related to CORS. It may just mean
that the request hasn’t finished yet because the server is taking a long time to respond. It’ll also be shown if you try
to access files directly off the file-system without using a web-server.

However, the most likely cause for a CORS request is that the preflight checks are failing. If the preflight OPTIONS
request fails then the main request won’t even be attempted, so there are no headers to show. There should be a
corresponding error message in the browser’s console.

How do the Authorization and WWW-Authenticate headers work with CORS?

The Authorization request header can be set as a custom header on a request and, just like any other custom header, it
would trigger a preflight OPTIONS request.

However, a bit like the Cookie header, the Authorization header can also be included automatically by the browser as
part of HTTP authentication. Ignoring the cross-origin aspect for a moment, the steps for this form of authentication
are:

  • The browser requests a URL.
  • The server responds with a 401 status code and a WWW-Authenticate header indicating the authentication scheme to
    be used.
  • The browser prompts the user for credentials.
  • The original request is retried with the credentials encoded into the Authorization header.
  • Subsequent requests to the same URL will automatically included the Authorization header.

For cross-origin requests the withCredentials flag must be set to true. In most browsers that should enable HTTP
authentication, the exception being Safari.

If the withCredentials flag is not set, or the user does not provide credentials, then the original 401 response will
be the response exposed via JavaScript.

If a preflight OPTIONS request is required then this must succeed without any authentication.

Oddly, the 401 response does not need to include the Access-Control-Allow-Credentials header for the browser to
prompt for credentials. However, it should be included anyway in case the user declines the credentials prompt. In that
scenario the 401 becomes the exposed response and then Access-Control-Allow-Credentials is required.

How can I include authorization headers on the preflight OPTIONS request?

You can’t. The server must be configured to respond to the preflight OPTIONS request without any authentication
headers being present.

If you’re using a filter or middleware layer on the server to block all unauthorized requests then you’ll need to
provide an exception for the preflight OPTIONS requests.

Does CORS support HTTP redirects?

Yes. CORS requests do support HTTP redirects so long as all the requests include the relevant CORS response headers.

When we say ‘HTTP redirects’ we mean using status codes such as 301, 302, 307 or 308 in conjunction with a
Location response header. For a typical AJAX request these redirects are performed automatically by the browser
without any explicit handling in client-side JavaScript.

The specification for CORS has gradually become more permissive towards redirects. The information presented here
reflects what is currently implemented in browsers but it is likely to continue to change.

A preflight OPTIONS request must not attempt a redirect. Instead the preflight should just return the usual response
headers required for the CORS checks to pass. The redirect can then be performed on the main request.

A potentially problematic scenario occurs if the redirect is to a URL with a different origin from the URL that was
originally requested. This is allowed but when the browser attempts the new request it will set the Origin header to
null. This is not a bug, it is a security precaution included by design.

Even if you aren’t intentionally using redirects there are two common ways that they can creep in:

  1. Redirecting from http to https.
  2. Redirecting to add or remove a trailing URL slash. e.g. A server may redirect http://example.com/api/users to
    http://example.com/api/users/ or vice-versa.

If not done correctly this can change the request method to GET, which can trigger other errors such as a 405
response from the server. Inadvertently changing to a GET request will also cause the request body to be dropped.

Often the simplest solution is to use the final URL instead and skip the redirect altogether.

Why is my Origin ‘null’?

Under some circumstances the request header Origin can be set to the special value null.

Note that this is the 4-character string "null", not to be confused with the null keyword used by many programming
languages.

Within the browser the value of location.origin can also be the string "null".

This special value is used whenever a proper origin value doesn’t exist or can’t be exposed for security reasons. The
request header may be null even if location.origin has a proper value.

Some examples:

  • If the page is being loaded directly off the file-system using the file: scheme, without a web-server, it is still
    allowed to make HTTP requests but the Origin header will be null.
  • Likewise, a page created using the data: scheme will have a null origin. e.g. <iframe src="data:text/html,...">.
    Here the ... would be the URI-encoded contents of a web page to show in the iframe. The web page inside the
    iframe would have a null origin.
  • An iframe using sandboxing, such as <iframe src="..." sandbox="allow-scripts">. Within the iframe the value of
    location.origin may be populated based on the src URL but any CORS requests will have a null origin.
  • An HTTP redirect on a CORS request that changes the target origin. Even if the original request had a proper Origin
    header the redirected request will have Origin: null.

It is still possible for null to pass a CORS check, just like for any other Origin value:

Access-Control-Allow-Origin: null

It has been suggested that the specification should be changed to prevent null matching itself, so it is possible this
may stop working in future. As there are many different ways for Origin to be null it is quite difficult to target a
specific case on the server. The Referer header may still be available in some cases as a hint to what the Origin
would have been but that isn’t reliable either. Generally it is recommended not to allow access from null origins
explicitly, though Access-Control-Allow-Origin: * can be used for genuinely open resources.

What are the security implications of CORS?

It’s almost impossible to provide a comprehensive list but here are some of the common concerns.

Yes, they can.

This kind of attack has always been possible, even with servers that don’t use CORS. The defence against these attacks
is typically two-fold:

  1. Authentication and authorization checks to ensure the user sending the request is allowed to make the request.
  2. Validation and/or sanitization of all the data on the request to ensure it’s in an acceptable form.

Relying on a UI or web browser to perform these checks isn’t sufficient, they need to be on the server.

So what’s the point of CORS if it can easily be bypassed?

CORS aims to stop someone making a request while pretending to be someone else.

Let’s say you open a webpage in your browser. The page you open is malicious: someone has put some JavaScript code into
the page that is trying to cause trouble. It fires off some HTTP requests to other websites pretending to be you. There
are two main varieties of mischief that it may try to inflict:

  1. Stealing data. e.g. It might send a request to your webmail and grab a copy of your emails.
  2. Changing data. e.g. Deleting the contents of your database or transferring money from your bank account or buying
    something on your behalf from an eCommerce site.

The Access-Control-Allow-Origin response header is primarily concerned with the first problem, stealing data. At the
network level the data is still transferred but if the Access-Control-Allow-Origin header doesn’t allow the current
origin then the malicious script can’t read the response.

For the second problem CORS has preflight requests. The potentially harmful request won’t even be attempted unless the
preflight allows it.

It is important to appreciate that a ‘malicious site’ may not have started out as malicious. You may have even created
it yourself. The problem is XSS vulnerabilities, which allow hackers to inject their own code into the site. When you
enable CORS to allow requests from other sites you aren’t just trusting the sites’ developers not to be malicious,
you’re also trusting them not to have any XSS vulnerabilities that leave your server exposed.

Hmmm. That raises more questions than it answers. For starters, how does this malicious site pretend to be me?

There are several options here.

The most obvious answer is cookies. If you’ve logged into a site and it uses cookies to identify you then those
cookies will be included by the browser on all requests to that site. The malicious script doesn’t need direct access to
the cookies, it just makes a request and the browser includes the cookie automatically.

This type of browser magic falls under the heading of ambient authority. The withCredentials flag is used to control
three types of ambient authority:

  1. Cookies.
  2. The Authorization header as part of HTTP authentication. This shouldn’t be confused with using the Authorization
    header explicitly as a custom request header, which is not ambient authority. See
    How do the Authorization and WWW-Authenticate headers work with CORS? for more information.
  3. TLS client certificates.

These forms of ambient authority could have been left out of CORS altogether and some initial implementations didn’t
allow them. However, enough developers wanted cookie support that the current compromise was eventually included.

If you’re using cookies and don’t need to support cross-origin requests then you should consider setting the directive
SameSite to either Strict or Lax. Browsers are gradually switching to Lax by default, away from the historical
default of None, but you don’t need to wait if you set it explicitly.

There are other forms of ambient authority that are less easy to avoid and which pose very real problems to the design
of CORS.

For example, a site could use IP addresses or network layout to prevent unauthorized access.

A common scenario is a site hosted on an internal network that allows access to anyone on that network. The ‘security’
here assumes that the site isn’t accessible outside the local network. An external hacker can’t send HTTP requests
directly to the server. However, if someone on the internal network opens the hacker’s malicious site then it can start
sending requests to those internal sites from within the browser. The page running in the browser is being used as a
bridge between the internal network and the outside world.

Router configuration pages are a particularly common example. Chances are your home internet connection includes a
router with a webpage to configure your home network.

For the ‘stealing data’ problem, why not just return an empty response instead?

For a new server you could do precisely that. However, CORS had to be designed to work with servers that already existed
and had no knowledge of CORS. Those servers won’t have the relevant response headers so the browser will prevent access
to the response.

The response in that scenario still makes it to the browser and would be accessible in the developer tools. That isn’t a
problem as the developer tools are only accessible to the person using the device. CORS isn’t trying to protect the data
from that person. Quite the opposite, that person is the potential victim of the data theft. CORS is trying to stop a
malicious script embedded in the page from accessing the response and passing it on to someone else.

Not all requests use a preflight. Doesn’t this leave the door wide open to the hackers in cases where they don’t need access to the response?

Yes, it does.

However…

It is a door that was already open before CORS was introduced. This particular vulnerability goes by the name CSRF (or
XSRF), which stands for cross-site request forgery.

Historically a CSRF attack could be performed in various ways but the most interesting is probably an HTML <form>.
Such a form could be submitted via a POST request from the malicious site to pretty much anywhere. The same-origin
policy did not prevent form submissions, it just prevented the source page from accessing the response.

Roughly speaking, the requests that don’t need a preflight are the same requests you could make using a <form>
instead.

While this is a security hole, it’s a hole that has existed for a long time and techniques have been developed to
protect against it. CORS just tries not to make the hole any bigger.

The withCredentials flag doesn’t trigger a preflight. Wouldn’t it be safer to always use a preflight check with cookies?

It would. But, again, CORS isn’t introducing any new security holes here. It’s just retaining the holes that already
existed with HTML forms.

The gradual shift by browsers towards defaulting to SameSite=Lax should help to protect cookies from CSRF abuse going
forward.

Why is a * value for Access-Control-Allow-Origin not allowed when using withCredentials?

The reasoning goes something like this:

  1. A * value exposes the content to any other webpage that wants it. This includes potentially malicious pages.
  2. If the content is always the same, no matter who requests it, then exposing it to everyone isn’t necessarily a
    problem.
  3. However, if the request requires withCredentials to be set then the content isn’t the same for everyone. Either
    access is restricted or the content varies by user. In this scenario the malicious page is now in a position to steal
    the version of the content that’s accessible to the current user.

Unfortunately, yes you can. Some libraries will even do this for you.

This is just as bad as using *. The only reason CORS doesn’t prevent it is because it can’t. There’s no way for the
browser to know that your server is indiscriminately echoing back the Origin header.

To be clear, there’s nothing wrong with echoing back the Origin header for specific, trusted origins. That’s precisely
how CORS is supposed to work. The problems arise when there aren’t adequate restrictions on the origins that are
allowed.

If you’re returning Access-Control-Allow-Credentials: true then you shouldn’t be echoing back all origins in
Access-Control-Allow-Origin. Chances are you have a gaping security hole. Worse, as discussed earlier, hosting your
site behind a firewall on an internal network is unlikely to protect you.

If I can’t use *, is there a way to allow all subdomains, e.g. *.example.com?

The CORS specification doesn’t allow for this. Only the exact value * is special and it can’t be used as a wildcard in
other values.

Configuring a server to echo back all origins for a particular domain can be quite tricky to get right. Consider the
following examples of origins:

http://example.com
https://www.example.com
http://evil-site-example.com
http://example.com.evil-site.com

We might want to support the first two origins, including http, https and all subdomains, but without matching the
other two. Those other two origins have example.com as a substring but they are totally unrelated domains that could
be under the control of anybody. Further, configuration based on regular expressions needs to be careful to escape the
. character to avoid it being treated as a wildcard.

You might think that no-one is going to bother attacking your site because it’s small and not worth the effort.
Unfortunately these exploits can easily be found just by using scripts that trawl the web looking for vulnerable sites.
These hackers (usually script kiddies) aren’t trying to attack your site specifically, they just set the script
running and wait for it to find a victim.

There’s also a problem with the premise of this question. You probably shouldn’t be trying to allow access to all
subdomains in the first place. If it isn’t possible to list all the relevant subdomains explicitly then it probably
isn’t safe to trust them all either. If any subdomain is running an application with an XSS vulnerability then it could
potentially be compromised.

I read somewhere that Access-Control-Allow-Origin: null is potentially insecure. Why?

If you aren’t familiar with the special origin value null then see Why is my Origin ‘null’?.

Part of the problem is that some developers mistakenly believe that returning null is equivalent to omitting the
header altogether. A bit of basic testing may even seem to confirm that.

The reality is that returning Access-Control-Allow-Origin: null will allow any request with Origin: null.

Generally you can’t set the Origin header in your client-side code, the browser will set it for you. However, it’s
relatively easy to use iframes or HTTP redirects to coerce the browser into sending Origin: null. So if you allow
requests from null you’re effectively allowing them from anywhere.

As we’ve already discussed, allowing requests from anywhere is fine under certain circumstances. However, in those
circumstances you can just use Access-Control-Allow-Origin: * instead.

Where can I read more about the security implications of CORS?

You might find these useful:

  • w3c.github.io
  • portswigger.net

What is an opaque response?

If you’re using the fetch API then you might have come across this message in Chrome:

If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

It’s shown at the end of some CORS error messages.

The first thing to appreciate is that disabling CORS does not mean disabling the same-origin policy. It is
important to be clear about the difference between the two.

An opaque response is an HTTP response where you can’t access any of the details in your JavaScript code. It is opaque
in the sense that you can’t look into it. That includes the status code, the headers and the body content. You may
notice that it’s very similar to what happens when a response fails the CORS checks.

To make a fetch request with an opaque response set the mode to 'no-cors':

fetch(url, {
  mode: 'no-cors'
}).then(response => {
  console.log(response.type) // logs the string 'opaque'
})

Some notes:

  • The mode is only relevant for a cross-origin request, it doesn’t matter for same-origin requests.
  • Any CORS response headers will be ignored. Even if they are included you won’t be able to read the response.
  • A GET request won’t include the Origin request header. It will still be included for POST requests, just like it
    would for same-origin requests.
  • Only simple requests that do not require a preflight are allowed.
  • There is no equivalent if you’re using XMLHttpRequest.

A request made using mode: 'no-cors' won’t undergo CORS checks in the browser, so the usual CORS error messages won’t
be shown. But other than suppressing the error messages, what use is it?

In practice the use cases are pretty limited, so if you’re seeing the error message mentioned earlier it is unlikely to
be the solution you want.

One use case is for requests where you handle success and failure exactly the same way. You try to tell the server to do
something but whether or not it succeeds doesn’t have any impact on the UI.

Another use case is caching. The requests can be used to pre-populate caches for things like stylesheets where you don’t
need to access the response details in JavaScript code.

You can suggest improvements to this page via
GitHub.

Cover image for How to Debug CORS Errors

Tim Perry

Your request is hitting an error due to CORS. Not all is lost! Most CORS errors are quick & easy to debug and fix, once you understand the basics. Let’s sort it out.

You know you’re hitting a CORS error when you see error messages like:

Access to fetch at ‘https://example.com’ from origin ‘http://localhost:8000’ has been blocked by CORS policy.

No ‘Access-Control-Allow-Origin’ header is present on the requested resource

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://example.com/

Response to preflight request doesn’t pass access control check

The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’

Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

Request header field custom is not allowed by Access-Control-Allow-Headers in preflight response.

In each of these cases, you’ve asked JavaScript running in your page to send a request to a different origin, and at some stage the browser is refusing to do what you want.

What is CORS?

When you include JavaScript in a web page, you’re running code on your user’s computer, inside their browsing session.

That’s a lot of power, and browsers are designed to protect users from the risks of this. CORS is one of these protections, aiming to protect the user and the services they use from two main attacks:

  • CORS stops you from using the user’s existing login session (their cookies and other cached authentication details) when communicating with other servers. JavaScript on your web page shouldn’t be able to send requests to the Facebook API using their existing Facebook session. Without CORS, any web page could talk to other servers as you.
  • CORS stops you from talking to servers that might only be accessible from their machine, but which aren’t accessible publicly. Your web page should not be able to send requests to my-intranet-server.local, which might be an internal company server or your home router, and it should not be able to talk to servers that are listening only for localhost requests. Servers like these are often unauthenticated and very trusting, because they aren’t connected to the public internet. Without CORS, any web page you visit could access them.

This only applies to cross origin requests, e.g. requests from https://example.com to https://google.com. The protocol, domain, and port all count as part of a URL’s origin, but the path does not, so https://example.com/abc and https://example.com/def have the same origin, but http://localhost:123 and http://localhost:456 do not.

CORS protects against the above attacks by requiring the target server to opt into receiving dangerous requests from the source server, and to opt in to allowing pages from other origins to read responses. The Facebook API and your local network servers can accept requests from web pages running on other origins if they want to, but only if they agree.

Why doesn’t my CORS work?

Your CORS request is failing because you’re sending a request that the target server hasn’t agreed to allow.

There’s two classes of CORS request:

  • ‘Simple’ cross-origin requests. There are basic requests that use no unsafe headers, don’t stream requests or responses, and only use HEAD, GET or POST methods (with limited safe content types). Any request that’s possible here would also be possible by e.g. loading an image or posting a form to the cross-origin request (and we can’t stop those, for huge backwards compatibility reasons).

    You can always send simple requests, but you might not be allowed to read the response.

  • ‘Preflighted’ cross-origin requests. These are more complex requests, that aren’t easy to send in other ways. A ‘preflight’ request will be sent to ask the server for permission before sending any of these requests, and if it’s rejected, you won’t be able to send the request at all.

    If the preflight request is successful, the real request is sent, and the final response to that still has to follow the same rules as a ‘simple’ response for you to be allowed to read it.

When a request is preflighted, before sending the real request the browser sends an OPTIONS request with headers explaining the real request that it wants to send. It expects a response including headers that explicitly allow the real request.

There’s three ways that this might hit an error:

  1. You’re sending a simple request, which is sent immediately, but the headers on the response don’t allow you to read it.
  2. You’re sending a preflighted request, and the headers on the preflight response don’t allow you to send the real request.
  3. You’re sending a preflighted request, the preflight went OK and the request was sent, but the headers on the final response for the real request don’t allow you to read it.

The browser error message should show you which is happening for you. You can know if your request is being preflighted by looking for an OPTIONS request that’s sent immediately before it.

The rules for the final (after preflight, if applicable) response are:

  • The response must include a Access-Control-Allow-Origin header, whose value either matches the page’s origin or is *. The page’s origin is sent in the request in an Origin header.
  • If the request included credentials (e.g. fetch(url, { credentials: 'include' })) then the response headers must include Access-Control-Allow-Credentials: true, and the Access-Control-Allow-Origin header must match exactly (i.e. * is not allowed).

If the response doesn’t follow those rules, then the server hasn’t opted in to your request, and you won’t be allowed to read the response.

If you’re in cases 1 or 3, you must be breaking one of these rules.

The rules for the preflight request are:

  • The preflight response must include a Access-Control-Allow-Origin header, whose value either matches the page’s origin or is *. The page’s origin is sent in the preflight request in an Origin header.
  • If the page wants to send custom headers, then it will include Access-Control-Request-Headers listing the headers in the preflight OPTIONS request, and the server must include a Access-Control-Allow-Headers header that includes all those headers in the response. * can also be used here, but it won’t match an Authorization header — that must always be listed explicitly.
  • If the page wants to use a non-simple HTTP method, it will include Access-Control-Request-Method in the preflight OPTIONS request, and the server must include a Access-Control-Allow-Methods header that includes that method in the response.
  • If the page wants to send credentials (e.g. fetch(url, { credentials: 'include' })) the response must include a Access-Control-Allow-Credentials: true header, and the Access-Control-Allow-Origin header must match exactly (i.e. * is not allowed).

If your preflight OPTIONS response doesn’t follow these rules, then you won’t be allowed to send the real request at all.

If you’re in case 2, you must be breaking one of these rules.

It’s also possible that you’re in case 2, but you actually don’t want to read the response — you just want to send it. To do that, you’ll need to simplify your request such that it’s a simple request. You can use { mode: 'no-cors' } on your fetch options to enforce this (but note that this doesn’t change the rules, it just enforces that it’s a simple request where you can’t read the result).

How can I fix my CORS error?

To know exactly why your request is failing, you need to inspect the traffic itself, find where you’re breaking the rules above, and then either:

  • Change the request to make it a simple request
  • Change the server’s response to follow the rules above
  • If all else fails, proxy the request through your own server on your own origin, so it’s not a cross-origin request (proxying avoids the attacks above, because it doesn’t let you use the cookies or authentication details from the user’s browser, and it requires the target server to be accessible from your source server)

To inspect the traffic, you can use your browser built-in tools, but it’s usually easier to use a dedicated HTTP debugger like HTTP Toolkit. Dedicated tools make it much easier to see the data, rather than (for example) Chrome’s very cramped and fiddly network tab, and you can also breakpoint responses and edit the headers to test how the browser will handle changes without actually changing your server. Also, some Chrome versions don’t show all CORS requests.

Hopefully, once you examine your CORS requests & responses, it’s clear where you’re breaking the rules above.

If not, try walking through Will It CORS. This is a self-explaining implementation of the CORS rules: you can input step by step what you’re trying to do, and it’ll tell you what will happen and why, and how you can change it.

There’s also a few common mistakes that you should watch out for:

  • Trying to request content from another origin that isn’t explicitly available cross-origin. If its not your server, and it doesn’t actively want CORS requests, you’re not going to work around most issues: you need to proxy the request, ask the owner to allow it, or do something entirely different.
  • Always returning * for Access-Control-Allow-Origin, and then trying to send credentials.
  • Adding CORS headers for preflight OPTIONS requests, but forgetting to also include CORS headers on the final request too.
  • Unnecessarily sending custom request headers. This will trigger a preflight request. You can often get by just using the CORS-safe request headers instead, or moving request data into the body of your request.
  • Incorrectnyl caching CORS response headers independent of their origin, by not using Vary: Origin. If you do this then responses for requests from one origin may be cached and returned for later requests from a different origin. That mismatched data can quickly break things.
  • Trying to access response headers without including an Access-Control-Expose-Headers header. In this case, all headers except the CORS-safe response headers will be unexpectedly undefined, even though they were sent by the server.
  • Sending cross-origin mixed-content requests (a request from https://... to http://...). These will always be blocked, regardless of the details, as insecure content like this is never allowed on HTTPS origins. There’s not much you can do about this, other than changing to use HTTPS on both servers.

That covers the core of CORS, how it can go wrong, and how to fix it. Have more questions? Comment below, or get in touch on Twitter.

Originally posted on the HTTP Toolkit blog

6 min read

You’ve created an API with Express and you’re busy adding some JavaScript to your front end which will make requests to it. Everything is going great until you load up the front end in your browser and you see a weird error like this in the console:

Access to fetch at 'https://your-api.com/user/1234' from origin 'https://your-website.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Perhaps you’ve then tried setting the request’s mode to no-cors as the error message suggests, but the request to the API still doesn’t work. Even after a bunch of Googling, it’s hard to wrap your head around why this is happening or how to get around it.

The good news is that there’s a library for Express which you can use to help fix these CORS errors, but before we look at fixing them, what do they actually mean? In order to understand these errors, let’s take a look at what CORS is.

What is CORS and why is it ruining your day?

CORS stands for Cross-Origin Resource Sharing, and it’s something which is supported by all modern browsers. It’s a bit of a mouthful, so we’re going to break it down first, and then we can learn about what it actually does.

What is a «resource»?

A resource is the content which is available at a specific URL e.g. an HTML web page, an image, or a JSON API response. It’s effectively the «stuff» which makes up the world wide web.

What is an «origin»?

The origin for a resource is the protocol + domain + port e.g. for the URL https://your-api.com:8080/user/1234 the origin is https://your-api.com:8080. If the URL doesn’t contain a port, then the origin will just be the protocol + domain.

What does Cross-Origin Resource Sharing actually do?

Cross-Origin Resource Sharing is the way in which a web browser ensures that the front end JavaScript of a website (origin A) can only access resources from another origin (origin B) if that origin explicitly allows it to. If it does allow it, then the resource is shared – you guessed it – cross-origin! Phew, we got there in the end.

CORS can help prevent malicious websites from accessing and using data from places that they shouldn’t be. When you see those annoying CORS errors in your browser, it’s actually your web browser doing its best to protect you from what it has identified as a potentially malicious request.

How does CORS work?

The way in which a web browser figures out whether a resource is allowed to be shared cross-origin is by setting an Origin header on requests made by front end JavaScript. The browser then checks for CORS headers set on the resource response. The headers it will check for on the response depend on the type of request which the browser has made, but the response must at least have the Access-Control-Allow-Origin header set to an allowed value for the web browser to make the response available to the front end JavaScript which requested it.

An example CORS request

An example of a cross-origin request would be a GET request made with fetch from the front end JavaScript on your web page – which is hosted on one domain (origin A) – to an API endpoint which you host on a different domain (origin B).

The request made by the browser from the JavaScript on your web page at https://your-website.com/user-profile would contain this information:

> GET /user/1234 HTTP/1.1
> Host: your-api.com
> Origin: https://your-website.com

The Origin request header is automatically set by the web browser – for security reasons you are not able to set its value when you make the request with fetch.

In order for the example CORS request above to work correctly, the response from your API would need to look like this:

< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: https://your-website.com
< Vary: Origin
< Content-Type: application/json; charset=utf-8
< 

{"name":"Existing Person"}

Notice how the value of the Access-Control-Allow-Origin response header matches the value of the Origin response: https://your-website.com. The web browser will see this CORS response header and determine that it has permission to share the response content with the front end JavaScript on your web page.

Now we have a better idea of what CORS is and what it does, it’s time to set some CORS headers and fix the errors you’re getting on your web page.

How to set CORS headers and get rid of those annoying errors

As you saw in the example above, it’s important for the web browser to send the Origin header in the request that it makes to your API, but it’s your API which needs to send the all important Access-Control-* headers in the response. These CORS headers are what will tell the web browser whether or not it is allowed to make the response from your API available to your front end JavaScript.

The library you’re going to use to help fix the CORS errors you’ve been battling is the cors middleware package. Head to the directory containing your Express application in your terminal, and let’s get it installed:

npm install cors

Note: In this blog post I’m linking to the cors package on GitHub instead of npm as at the time of writing the documentation for this package is out-of-date on on npm.

Once it’s installed, you need to require it in your application (directly after you require express is fine):

const cors = require("cors");

If you call the cors middleware in your Express application without passing any configuration options, by default it will add the CORS response header Access-Control-Allow-Origin: * to your API’s responses. This means that any origin — i.e. a web page on any domain — can make requests to your API. Unless you’re building an API for the general public to use, this is not the behaviour you want, so let’s jump right in to configuring the cors middleware so that only your website can make CORS requests to your API:

/**
 * These options will be used to configure the cors middleware to add
 * these headers to the response:
 * 
 * Access-Control-Allow-Origin: https://your-website.com
 * Vary: Origin
 */
const corsOptions = {
	origin: "https://your-website.com"
};

/**
 * This configures all of the following routes to use the cors middleware
 * with the options defined above.
 */
app.use(cors(corsOptions));

app.get("/user/:id", (request, response) => {
	response.json({ name: "Existing Person" });
});

app.get("/country/:id", (request, response) => {
	response.json({ name: "Oceania" });
});

app.get("/address/:id", (request, response) => {
	response.json({ street: "Gresham Lane", city: "Lakeville" });
});

Typically you’ll want to enable CORS for all of the routes in your Express application as in the example above, but if you only want to enable CORS for specific routes you can configure the cors middleware like this:

/**
 * These options will be used to configure the cors middleware to add
 * these headers to the response:
 * 
 * Access-Control-Allow-Origin: https://your-website.com
 * Vary: Origin
 */
const corsOptions = {
	origin: "https://your-website.com"
};

// This route is using the cors middleware with the options defined above.
app.get("/user/:id", cors(corsOptions), (request, response) => {
	response.json({ name: "Existing Person" });
});

// This route is using the cors middleware with the options defined above.
app.get("/country/:id", cors(corsOptions), (request, response) => {
	response.json({ name: "Oceania" });
});

/**
 * We never want this API route to be requested from a browser,
 * so we don't configure the route to use the cors middleware.
 */
app.get("/address/:id", (request, response) => {
	response.json({ street: "Gresham Lane", city: "Lakeville" });
});

Enabling «complex» CORS requests

The examples above configure CORS for simple GET requests. For many other types of CORS requests, a CORS «preflight» request will be made by web browsers before the actual CORS request. This preflght request uses the OPTIONS HTTP method and it helps the browser determine whether it will be allowed to make the CORS request.

The cors middleware provides instructions for Enabling CORS Pre-Flight, and allows you to configure the headers that you want to send in the response to a preflight request.

Fear CORS no more

Hopefully this article has helped you understand what CORS is all about, but there will always be times where it’s difficult to figure out how you need to configure things for a CORS request to work. Here are some things that have helped me out along the way:

  • Will it CORS? — This fantastic tool will ask you about what you want to do, and then tell you the exact CORS response headers that you need to send for the CORS request to work correctly.
  • CORS HTTP headers — A handy reference which lists all of the CORS headers which you can use.
  • Simple requests and Preflighted requests — The CORS documentation on the Mozilla Developer Network has great explanations of the different types of CORS requests.
  • Path of an XMLHttpRequest(XHR) through CORS — This flowchart on Wikipedia is a helpful visual tool for understanding when a CORS request is considered «complex».
  • Fetch standard: HTTP extensions — This documentation covers the nitty gritty details of how CORS is implemented in the browser Fetch API.

Happy cross-origin resource sharing!

Понравилась статья? Поделить с друзьями:
  • Post error при запуске компьютера
  • Post error pause что это
  • Post error occurs что это такое
  • Post error occurs что делать
  • Post error occurs что выбрать