Aug 28, 2023
#CORS#Web Development#Security#Frontend#Backend
Table of Contents
Introduction
In Part 1, we covered CORS basics, and in Part 2, we explored Fetch API modes. Now, let's dive into the more advanced aspects of CORS: handling different HTTP methods, custom headers, and managing credentials.
When working with complex cross-origin requests, understanding how different CORS headers interact becomes crucial. Let's explore each important header in detail.
Access-Control-Allow-Methods
This header is crucial when your API supports multiple HTTP methods. Here's how it works:
app.options("/products", cors({
methods: ['DELETE'],
origin: function (origin, callback) {
if(allowedOrigins.indexOf(origin) !== -1){
callback(null, origin)
} else {
callback(null, null);
}
}
}))
If we try a PUT
request without the proper method configuration:
After adding the correct method:
app.options("/products", cors({
methods: ['DELETE', 'PUT'],
When your requests need custom headers, you need to explicitly allow them:
app.options("/products", cors({
methods: ['DELETE', 'PUT'],
allowedHeaders: ['X-Token-Age'],
origin: function (origin, callback) {
if(allowedOrigins.indexOf(origin) !== -1){
callback(null, origin)
} else {
callback(null, null);
}
}
}))
Let's try sending multiple custom headers:
fetch(`http://localhost:3004/products`, {
method: "PUT",
mode: "cors",
headers: {
"X-Token-Age": "3600",
"X-Token-Length": "1800"
}
})
Without proper configuration, we get an error:
But with proper configuration it will work properly
app.options("/products", cors({
methods: ['DELETE', 'PUT'],
allowedHeaders: ['X-Token-Age', 'X-Token-Length'],
...
Working with Credentials
Understanding Credentials in CORS
The Access-Control-Allow-Credentials
header is essential when your application needs to send cookies or authentication headers in cross-origin requests.
fetch(`http://localhost:3004/products`, {
method: "PUT",
mode: "cors",
headers: { "X-Token-Age": "3600"},
credentials: "include"
})
Without proper credentials configuration in server, you'll see this error:
To fix this, configure your server:
app.options("/products", cors({
methods: ['DELETE', 'PUT'],
allowedHeaders: ['X-Token-Age'],
credentials: true,
origin: function (origin, callback) {
if(allowedOrigins.indexOf(origin) !== -1){
callback(null, origin)
} else {
callback(null, null);
}
}
}))
Cookie Behavior in Cross-Origin Requests
When working with cookies across domains, there are important considerations:
-
Domain Limitations
document.cookie = "token=wqerbkljasdfhy;domain=stackoverflow.com;"
-
Subdomain Sharing
- Cookies can be shared between subdomains of the same root domain
- Set the domain attribute to your root domain
- Example:
.example.com
allows sharing between api.example.com
and www.example.com
Cookie Security Attributes
Important cookie attributes for secure applications:
res.cookie('sessionId', 'abc123', {
domain: '.example.com',
secure: true,
httpOnly: true,
sameSite: 'Strict'
});
-
Secure Attribute
- Ensures cookies are only sent over HTTPS
- Essential for production environments
-
HttpOnly Attribute
- Prevents JavaScript access to cookies
- Protects against XSS attacks
-
SameSite Attribute
- Controls how cookies behave in cross-site requests
- Options: Strict, Lax, None
Best Practices
-
Method Handling
app.use(cors({
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
origin: allowedOrigins
}));
-
Security Headers
- Always use specific origins instead of wildcards
- Implement proper preflight handling
- Keep allowed headers list minimal
-
Cookie Security
app.use(session({
cookie: {
secure: true,
httpOnly: true,
domain: '.example.com',
sameSite: 'Strict'
}
}));