Cross-origin issues are a classic topic in web development and one of the most fundamental ones. They commonly occur in projects built with a decoupled frontend-backend architecture.
Cross-origin typically refers to the restrictions imposed by the browser’s Same-Origin Policy. When any of the scheme/protocol (http/https), domain, or port differs, the requests are considered to be from “different origins,” and the browser restricts access to those resources.
For instance, when a frontend fetch calls a backend API, it will throw an error:
Access to fetch at 'https://api.xxx.com' from origin 'http://localhost:3000'
has been blocked by CORS policy
You can fix this simply by configuring the backend response headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Preflight OPTIONS requests must also be allowed. Login requests are mostly JSON, which will trigger a preflight (OPTIONS) request.
However, there is another issue that many tutorials fail to mention or simply assume the reader already knows: Access-Control-Allow-Origin can only accept a single origin value. If you need to allow multiple origins, you need to add conditional logic on the backend to dynamically return the Origin:
const allowList = [
'https://a.com',
'https://b.com'
]
const origin = req.headers.origin
if (allowList.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin)
}
Cross-Origin Cookie Issues
A very common scenario is this: the frontend (app.lycois.org) sends a POST request to the login endpoint (api.lycois.org/auth/login), and the HTTP 200 response contains a Set-Cookie: token=... header. However, when the client subsequently requests api.lycois.org/auth/me, the cookie is not sent, or rather, the cookie failed to be set in the first place.
Allowing Credentials on Frontend and Backend
In cross-origin requests, if you want to allow credentials, you must set Access-Control-Allow-Credentials: true.
fetch("https://api.xxx.com", {
credentials: "include"
})
What are credentials? In the browser, Cookies, HTTP authentication details (Basic Auth, Authorization header), and TLS client certificates are all considered credentials.
Therefore, without include/withCredentials: cookies will neither be sent nor stored.
The backend CORS configuration must also explicitly allow credentials. Additionally, note that in this setup, Access-Control-Allow-Origin cannot be * (requests carrying cookies require an exact match domain).
Cookie Attributes Must Be Compatible with Cross-Site (SameSite)
Modern browsers are quite strict. Cross-site cookies must follow this format:
Set-Cookie: session=xxx; Path=/; HttpOnly; Secure; SameSite=None
-
SameSite=None: Allows cookies to be sent cross-site (key to cross-origin login). -
Secure: Requires HTTPS (without HTTPS,SameSite=Nonecookies will be rejected by the browser). -
HttpOnly: Prevents JavaScript from reading the cookie (more secure).
Local Development
During local development, cross-origin issues also arise because the ports of the frontend and backend are different.
For example, cross-origin cookie blocking occurs—especially given Chrome’s strict enforcement that SameSite=None requires Secure, which often prevents cookies from being successfully written over HTTP on localhost.
Because cross-origin cookies are tedious to configure and prone to pitfalls across different browsers (especially Safari’s Intelligent Tracking Prevention, or ITP) and complex network environments, many modern decoupled projects choose to use cookies only as a fallback mechanism, or drop cookies entirely in favor of Tokens (JWT).