CORS implementation
CORS (Cross-Origin-Resource-Sharing) is security feature that specifies the origins from which client frontend apps can make cross-origin requests, implemented by the server and enforced by the browser. It is necessary to avoid CSRF (Cross-Site-Request-Forgery).
When the frontEnd sends a cross-origin fetch request, the browser automatically switches from enforcing the same-origin policy to CORS mode. In this mode, the browser checks for CORS headers in the server response. If the headers are valid, the browser allows the response data to be accessible on the client side.
//Client cross-origin fetch request
async function passa(e){
e.preventDefault()
let richiesta={
method: "GET",
}
let response = await fetch("http://localhost:3030/aggiungi", richiesta)
...
}
//CORS implementation on the server.js
app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
next()
});
//server method response
app.get("/aggiungi", (req, res)=> {
res.send( [{"prima": "andante"}, {"take": "it easy"}] )
})

We can npm install cors module to implement CORS more easily. A cors without its options object allows any fetch request.
//Implementing the cors middleware
const cors = require("cors");
app.use( cors() )
The CORS module options object customizes the server's CORS behavior, and is equivalent to defining a custom middleware function.
Each option property value is equivalent to a response header.
const cors = require("cors");
const corsOptions = {
origin: "http://localhost:10000",
methods: [`DELETE`, `PUT`],
allowedHeaders: ["Content-Type"],
};
app.use( cors(corsOptions) )
Cors simple options properties.
The CORS option properties configure the server response headers, but some are specific to simple requests while others are specific to preflight requests.
//complete cors options object
let corsOptions = {
origin: ['https://example.com', 'https://another-example.com'],
allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
exposeHeaders: ['Content-Length', 'X-Custom-Header'],
credentials: true,
methods: ['GET', 'PUT', 'DELETE'],
maxAge: 3600,
}
We define as simple requests any request that doesn't require a preflight check, doesn't pose a risk of modifying server data in a harmful way, and uses safe HTTP methods such as GET, HEAD, OPTIONS, or POST with a limited set of content types.
The cors options properties used on simple requests are origin, credentials, allowHeaders and exposeHeaders.
The origin property specifies the protocol, host, and port of the frontend client that's allowed to make the fetch request. By default, its value is *, which allows requests from all origins.
//The Access-Control-Allow-Origin in the server response
//Protocol: HTTP, Host/domain: localhost, Port 3000
//The Port may be already included in the frontEnd URL
const corsOptions = {
origin: "http://localhost:3000"
};
The allowedHeaders property specifies the header data types that are allowed to be sent from the fetch request, including custom headers (which will trigger a preflight), used to stop malicious scripts,
The safelisted CORS headers that won't trigger a preflight are Accept, Accept-Language, Content-Language, and Content-Type with specific values like multipart/form-data, or text/plain. Any non-safelisted header not included in the allowedHeaders will block the cors request.
//This client header won't trigger a preflight
let fetchOption = {
method: "POST",
body: "mode=cors",
headers: {
"Content-Type": "text/plain",
"Custom": "element"
}
}
//We can remove Content-Type, but not Custom
const corsOptions = {
origin: "http://localhost:3000",
allowedHeaders: ["Content-type", "Custom"],
};
The allowedHeaders will trigger a preflight request for custom headers or non-safelisted header values.
The exposeHeaders property controls which response headers are accessible on the client-side, preventing sensitive data exposure. By default, it's an empty array, only allowing a limited set of safe headers (e.g. Cache-Control, Content-Language, Content-Type) to be visible, without requiring explicit declaration.
The exposeHeaders property bypasses the server's permission check, enabling direct access to response headers in the client, thereby avoiding an additional OPTIONS request for each header access.
//We can't edit the headers in the client
async function sentData(){
let oggetto= {
method: "POST",
body: JSON.stringify({"user1": "dude"}),
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"token": "temperatura"
}
}
let resp = await fetch("http://localhost:3030/heads", oggetto)
console.log(resp.headers); // Log all response headers
console.log(resp.headers.get("token")); // Get specific header token
let json = await resp.text()
}
If a header is not included in exposeHeaders, it will be inaccessible to the client-side code, even if the server set() it. It affects only the CORS response headers and won't block the cors request nor trigger a preflight.
//Server side code
//The Content-Type is already being exposed
const cors = require("cors");
const corsOptions = {
origin: "http://localhost:3000",
allowedHeaders: ["token"], //if removed blocks the cors request
exposedHeaders: ["token"], //if removed hides the header.get() on client
};
app.use(cors(corsOptions));
//All fetch headers are avaiable in the server, even without exposeHeaders
app.post("/heads", (req, res)=>{
console.log( "req.body" )
console.log( req.headers['content-type'] ) //application/json
//To be avaiable in the client headers need to be set(), even without send()
res.header("Content-Type", "application/json");
res.send("non ci siamo")
})
In a CORS request, the credentials property is used in both the client-side fetch request and the server-side CORS option object, but with different purposes. On the client-side, credentials specifies whether and from which origin the credentials will be shared.
In the client fetch request it can take one of three values, omit (default), which excludes credentials; same-origin, which includes credentials only if the request is made to the same origin as the server; or include, which includes credentials regardless of the request origin.
//Fetch request credentials
let oggetto= {
method: "POST",
body: JSON.stringify({"sent": "data"}),
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
credentials: `include`
}
On the server-side, the credentials property in the CORS option object is a boolean value that indicates whether the server allows credentials to be included in the request.
//The Access-Control-Allow-Credentials in the server response
const corsOptions = {
origin: "http://localhost:3000",
credentials: true, //false won't block the fetch request
};
Cors options and preflight requests
The maxAge and methods properties are only triggered during the browser's preflight OPTIONS request.
A preflight request returns the headers and methods allowed by the server, it triggers based on fetch options properties that may cause side-effects to the server, like PUT/DELETE methods and non-safelisted allowHeaders values.
The browser initiates the preflight request and checks the response cors headers to allow the fetch request.
//The preflight request includes additional headers compared to the fetch request:
Access-Control-Request-Method: It contains the HTTP methodthat will be used
in the actual fetch request.
Access-Control-Request-Headers: It contains a comma-separated list of the
non-simple headers that will be used in the actual fetch request.
//The server will respond with the CORS headers:
Access-Control-Allow-Method/Request

The methods property specifies the array of methods allowed to be sent in the request.
//It will block only non-simple methods
//To limit GET, POST and HEAD methods use headers
const cors = require("cors");
const corsOptions = {
origin: "http://localhost:3000",
methods: ["PUT",`DELETE`]
};
app.use(cors(corsOptions));
The maxAge property sets the validity period cache of a preflight OPTIONS response cache, in seconds, any successive request from the same origin/method will skip its preflight request.
The browser has a built-in cache period for OPTIONS responses and can store multiple OPTIONS cache, no cache will be shared even if from the same origin.
//The Access-Control-Max-Age Server response header ONLY in the preflight response
const corsOptions = {
origin: "http://localhost:3000",
maxAge: 20,
};
Different responses return different HTTP codes, a preflight OPTIONS response has code 204, success but no return content, a server response has code 200, success and return content, and cached responses have code 304, no modified resource.
The ETag (Entity Tag) is a response header that identifies a specific version of a resource. It prevents simultaneous updates of the same resource and ensures that clients don't retrieve outdated content. The ETag is included in all responses, regardless of whether the response is cacheable or not.
The If-None-Match request header is used in conjunction with the ETag. It contains the cached ETag value from a previous response and is sent with subsequent requests to check if the resource has been updated. If they match then the cached response value is used.
The If-None-Match header is limited to GET and HEAD requests, which retrieve resources, and is not included in requests that edit or update data, such as POST, PUT, and DELETE, as they do not return a response resource.

1
Last updated
Was this helpful?