CORS (Cross-Origin Resource Sharing)
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that determines whether JavaScript running on one origin is allowed to access resources from another origin. It is enforced by browsers, not by servers.
Consider the following setup:
Frontend: http://localhost:5173
Backend: http://localhost:5000
Although both are running on your machine, they have different ports, making them different origins. Because of this, the browser applies CORS rules.
What Is an Origin?
An origin is defined by three components: Protocol + Domain + Port
For example:
http://localhost:5173
Simple Requests
A simple request is one that browsers consider low risk. Typically, these are GET, POST, or HEAD requests using only a limited set of headers and content types. When a simple cross-origin request is made, the browser automatically includes an Origin header.
GET /users HTTP/1.1
Origin: http://localhost:5173
The server checks the origin and decides whether to allow it. If allowed, it responds with Access-Control-Allow-Origin header containing the client's domain (or *):
Access-Control-Allow-Origin: http://localhost:5173
The browser compares the request's Origin with the value of Access-Control-Allow-Origin.
If they match, JavaScript is allowed to access the response. If the header is missing or doesn't match, the browser blocks access to the response and throws a CORS error.
A simple cross-origin request can still be fully processed by the server. CORS only determines whether the browser exposes the response to frontend JavaScript.
What Makes a Request "Simple"?
A request is considered simple only if it satisfies all of the following conditions.
Allowed Methods
GETPOSTHEAD
Allowed Request Headers
Examples include:
AcceptContent-Type
Allowed Content Types
The Content-Type must be one of:
application/x-www-form-urlencodedmultipart/form-datatext/plain
application/jsonis not considered a simple content type.
Preflight Requests
When a request doesn't satisfy the conditions for a simple request, the browser first sends a preflight request using the OPTIONS method.
fetch("http://localhost:5000/login", {
method: "POST",
headers: {
Authorization: "Bearer xyz",
"Content-Type": "application/json"
}
});
This is not a simple request because:
- It includes an
Authorizationheader. Content-Type: application/jsonis not a simple content type.
Instead of sending the POST immediately, the browser first asks the server whether this request is allowed.
Step 1: Browser Sends a Preflight Request
OPTIONS /login HTTP/1.1
Origin: http://localhost:5173
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type
This request contains:
- the origin making the request
- the HTTP method that will be used
- any non-simple headers that will be included
Step 2: Server Responds
If the request is allowed, the server replies with:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Authorization, Content-Type
These headers tell the browser that the actual request is permitted.
Step 3: Browser Sends the Actual Request
After receiving a successful preflight response, the browser sends the original request.
POST /login HTTP/1.1
Authorization: Bearer xyz
Content-Type: application/json
If the preflight request fails (for example, the server rejects the OPTIONS request or omits the required CORS headers), the browser never sends the actual request.
Note: CORS is enforced only by web browsers. Tools like Postman, curl, or server-to-server requests are not restricted by CORS.
Server Implementation
Most web frameworks provide CORS middleware that automatically handles CORS headers and preflight (OPTIONS) requests. Without such middleware, you'll need to return the appropriate Access-Control-Allow-* headers and handle OPTIONS requests yourself.