DEV Community

Cover image for Understanding CORS: A Tutorial for Beginners
Akash for MechCloud Academy

Posted on

Understanding CORS: A Tutorial for Beginners

What is CORS?

Cross-Origin Resource Sharing (CORS) is a browser-based security mechanism that controls how web applications (e.g., Single Page Applications or SPAs) can request resources from a different origin (domain, protocol, or port) than their own. It’s designed to prevent malicious websites from accessing sensitive data in a user’s browser without permission.

  • Origin: An origin is defined by the combination of protocol (e.g., https), domain (e.g., example.com), and port (e.g., 443). For example:

    • https://app.example.com and https://api.example.com are different origins.
    • https://example.com:8080 and https://example.com:3000 are different origins.
  • Same-Origin Policy: Browsers enforce this policy, which restricts web pages from making requests to a different origin unless explicitly allowed by the server. CORS is the mechanism that allows servers to relax this restriction.

Why Does CORS Exist?

CORS protects users from cross-origin attacks in browsers. For example:

  • A malicious site (evil.com) could try to fetch data from api.bank.com using a user’s browser, exploiting their active session (e.g., cookies or authentication tokens).
  • Without CORS, the browser would send the request, and if the server responds, evil.com could steal sensitive data.
  • CORS ensures the server specifies which origins (e.g., bank.com) are trusted, and the browser blocks responses to untrusted origins.

Why is CORS Enforced at the Server Level?

CORS is implemented at the server level (not the web application or browser level) for these reasons:

  1. Server Authority: The server hosting the API decides who can access its resources. It uses CORS headers (e.g., Access-Control-Allow-Origin) to specify trusted origins.
  2. Security: Clients (browsers or SPAs) can be manipulated or compromised. If CORS were enforced client-side, malicious scripts could bypass restrictions.
  3. Consistency: Server-level control ensures uniform access policies across all clients, avoiding inconsistent behavior.
  4. Non-Browser Clients: APIs often serve non-browser clients like CLIs, which aren’t subject to the Same-Origin Policy. Server-level CORS allows flexibility for these clients without affecting browser security.

Why Can CLIs Bypass CORS?

CLIs (e.g., curl, Python scripts) don’t run in a browser, so they aren’t subject to the Same-Origin Policy or CORS restrictions. They can call any API endpoint directly, assuming the API is accessible (e.g., no authentication or firewall blocks).

  • Why this is okay: CLIs don’t typically have access to a user’s browser session (e.g., cookies), so they can’t exploit browser-specific vulnerabilities. APIs use other security measures (e.g., API keys, OAuth) to secure access from non-browser clients.
  • Server’s role: The server still controls access for CLIs through authentication, rate limiting, or IP whitelisting, separate from CORS.

How Does CORS Work?

  1. Client Makes a Request: A web application (e.g., an SPA) sends an HTTP request to a different origin using fetch or XMLHttpRequest.
  2. Server Responds with CORS Headers: The server includes headers like:
    • Access-Control-Allow-Origin: Specifies which origins are allowed (e.g., https://app.example.com or * for all origins).
    • Access-Control-Allow-Methods: Allowed HTTP methods (e.g., GET, POST).
    • Access-Control-Allow-Headers: Allowed headers in requests.
  3. Browser Checks Headers: The browser compares the requesting origin with the Access-Control-Allow-Origin header:
    • If the origin is allowed, the browser delivers the response to the web application.
    • If not, the browser blocks the response, and the web application sees an error.
  4. Preflight Requests: For certain requests (e.g., POST with custom headers), the browser sends an OPTIONS preflight request to check if the server allows the request. The server must respond with appropriate CORS headers.

Practical Example: CORS in Action

Let’s create a simple SPA that fetches data from an API, along with a server that demonstrates CORS behavior. We’ll also show how a CLI can access the same API without CORS restrictions.

Step 1: Set Up the API Server

We’ll create a simple Node.js server using Express that serves data and includes CORS headers.

const express = require('express');
const app = express();

// Middleware to add CORS headers
app.use((req, res, next) => {
  // Allow requests only from http://localhost:3000
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
  next();
});

// Sample API endpoint
app.get('/data', (req, res) => {
  res.json({ message: 'Hello from the API!' });
});

// Handle preflight OPTIONS requests
app.options('/data', (req, res) => {
  res.sendStatus(200);
});

app.listen(4000, () => {
  console.log('API server running on http://localhost:4000');
});
Enter fullscreen mode Exit fullscreen mode

Save this as server.js. To run it:

  1. Install Node.js and Express: npm install express.
  2. Run the server: node server.js.

This server:

  • Allows CORS requests only from http://localhost:3000.
  • Responds to GET /data with a JSON message.
  • Handles preflight OPTIONS requests.

Step 2: Create the SPA

Create a simple HTML file with JavaScript to fetch data from the API. Serve it from http://localhost:3000.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>CORS Demo SPA</title>
</head>
<body>
  <h1>CORS Demo</h1>
  <button onclick="fetchData()">Fetch Data</button>
  <p id="result"></p>

  <script>
    async function fetchData() {
      try {
        const response = await fetch('http://localhost:4000/data');
        const data = await response.json();
        document.getElementById('result').textContent = data.message;
      } catch (error) {
        document.getElementById('result').textContent = 'Error: ' + error.message;
      }
    }
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Save this as index.html. To serve it:

  1. Use a simple HTTP server like Python’s: python -m http.server 3000.
  2. Open http://localhost:3000 in a browser.

This SPA:

  • Makes a fetch request to http://localhost:4000/data.
  • Displays the API response or an error.

Step 3: Test CORS Behavior

  1. Successful Request:

    • Open http://localhost:3000 in a browser and click “Fetch Data.”
    • Since the server allows http://localhost:3000 (via Access-Control-Allow-Origin), the request succeeds, and you’ll see: Hello from the API!
  2. CORS Failure:

    • Serve the same index.html from a different origin, e.g., http://localhost:5000 (run python -m http.server 5000).
    • Open http://localhost:5000 and click “Fetch Data.”
    • The browser will block the response because http://localhost:5000 isn’t allowed by the server’s CORS headers. You’ll see an error like: CORS policy: No 'Access-Control-Allow-Origin' header.
  3. CLI Access (No CORS Restrictions):

    • Use a CLI tool to call the API:
     curl http://localhost:4000/data
    
  • Output: {"message":"Hello from the API!"}
  • The CLI request succeeds because CORS doesn’t apply outside browsers.

Step 4: Experiment with CORS Settings

Modify the server’s CORS headers to see different behaviors:

  • Allow all origins: Change Access-Control-Allow-Origin to '*'. Now, any origin (e.g., http://localhost:5000) can access the API from a browser.
  • Disable CORS: Remove the Access-Control-Allow-Origin header. Browser requests will fail, but CLI requests will still work.

Key Takeaways

  • CORS is Browser-Only: It protects web applications from unauthorized cross-origin requests in browsers but doesn’t affect CLIs or other non-browser clients.
  • Server-Level Enforcement: The server uses CORS headers to control access, ensuring security and consistency. Client-side enforcement would be insecure.
  • CLI Flexibility: CLIs bypass CORS because they don’t run in browsers, but APIs use authentication or other mechanisms to secure non-browser access.
  • Practical Use: CORS enables SPAs to safely fetch data from different origins while preventing malicious sites from exploiting user sessions.

Top comments (0)