Aug 17, 2023
#CORS#Web Development#Fetch API#Frontend#JavaScript
Table of Contents
Introduction
In Part 1, we explored the fundamentals of CORS and its basic implementation. Now, let's dive deeper into the different modes available in the Fetch API and how they interact with CORS policies.
Fetch API Modes Overview
The Fetch API provides several modes that control how cross-origin requests are handled:
same-origin
(default)
cors
no-cors
navigate
Each mode serves a specific purpose and comes with its own set of rules and limitations.
Same-Origin Mode
The default mode in Fetch API is same-origin
. As we discovered in Part 1, this is the most restrictive mode.
fetch('http://localhost:3004/products')
Remember: A request is considered cross-origin if any of these differ:
- Protocol (http/https)
- Domain
- Port number
Even a different port on the same domain triggers a CORS error:
CORS Mode
When working with cross-origin requests, cors
mode is your primary tool:
fetch('http://localhost:3004/products', {
mode: 'cors'
})
This mode enables:
- Sending the
Origin
header
- Reading the response if proper CORS headers are present
- Supporting preflight requests when necessary
With proper server configuration:
const cors = require('cors');
app.use(cors());
The request succeeds with proper CORS headers:
No-CORS Mode
The often misunderstood no-cors
mode:
fetch('http://localhost:4000/customers', {
mode: 'no-cors'
})
While this mode appears to work (no CORS errors), it comes with significant limitations:
Key limitations:
- Response type is "opaque"
- Response body is null
- Cannot access response data in your code
- Status code is always 0
Use cases for no-cors
:
- Caching responses
- Loading images or resources
- Analytics requests
- Cases where response data isn't needed
CORS Configurations
Managing Origins
For production applications, you'll want to restrict allowed origins:
const allowedOrigins = ["http://localhost:3000", "https://example.com"]
app.use(cors({
origin: function (origin, callback) {
if(allowedOrigins.indexOf(origin) !== -1){
callback(null, origin)
} else {
callback(null, null);
}
}
}));
Handling Credentials
When working with authenticated requests:
fetch('http://api.example.com/data', {
mode: 'cors',
credentials: 'include'
})
app.use(cors({
credentials: true,
origin: 'http://localhost:3000'
}));
Best Practices
-
Mode Selection
- Use
cors
mode for cross-origin requests
- Stick with default
same-origin
for same-origin requests
- Only use
no-cors
when response data isn't needed
-
Security
- Never use
*
for Access-Control-Allow-Origin
in production
- Maintain a whitelist of allowed origins
- Be specific with allowed methods and headers
-
Error Handling
fetch('http://api.example.com/data', {
mode: 'cors'
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.catch(error => {
console.error('CORS or network error:', error);
});
-
Development vs Production
- Use permissive CORS settings in development
- Implement strict origin checks in production
- Monitor CORS errors in production logs
What's Next?
In the next part of this series, we'll explore:
- Understanding
Access-Control-Allow-Methods
- Working with
Access-Control-Allow-Headers
- Managing credentials with
Access-Control-Allow-Credentials
Until then, remember that CORS is not just a hurdle to overcome but a crucial security feature that protects users when implemented correctly.
[Continue to Part 3: CORS: Headers, Methods, and Credentials...]