keecode logokeecode
Beginner
cors
cross origin resource sharing
cors error
cors explained
access control allow origin
cors policy

Understanding CORS: A Complete Beginner's Guide

Learn what CORS is, why it exists, and how to fix CORS errors. Beginner-friendly guide to Cross-Origin Resource Sharing with practical examples.

Updated January 15, 2025

CORS (Cross-Origin Resource Sharing) is a security feature that often confuses developers. This comprehensive guide explains what CORS is, why it exists, and how to fix common CORS errors.

Table of Contents

  1. What is CORS?
  2. The Same-Origin Policy
  3. How CORS Works
  4. Common CORS Errors
  5. Fixing CORS Issues
  6. CORS in Different Frameworks
  7. Security Best Practices

What is CORS?

CORS (Cross-Origin Resource Sharing) is a security mechanism that allows or restricts web pages from making requests to a different domain than the one that served the web page.

Why CORS Exists

CORS exists to protect you from malicious websites. Without CORS, any website could:

  • Read your emails from Gmail
  • Transfer money from your bank account
  • Post to your social media
  • Access your private data from other sites
WITHOUT CORS (Dangerous):
evil-site.com → Can read your gmail.com emails
evil-site.com → Can access your bank.com account
evil-site.com → Can post to your facebook.com

WITH CORS (Safe):
evil-site.com → ❌ Blocked by Gmail
evil-site.com → ❌ Blocked by Bank
evil-site.com → ❌ Blocked by Facebook

Only authorized sites can access your data!

Real-World Example

// Your website: https://myapp.com
// API server: https://api.example.com

// This request will trigger CORS check:
fetch('https://api.example.com/users')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error('CORS Error:', err));

// Browser thinks: "myapp.com wants to access api.example.com"
// Browser checks: "Does api.example.com allow requests from myapp.com?"
// If YES → Request succeeds ✅
// If NO → CORS error ❌

The Same-Origin Policy

Before understanding CORS, you need to understand the Same-Origin Policy.

What is an Origin?

An origin consists of three parts:

  1. Protocol (http vs https)
  2. Domain (example.com)
  3. Port (80, 443, 3000, etc.)
https://example.com:443/page
└─┬──┘ └────┬─────┘└┬┘
Protocol  Domain   Port

All three must match for same origin!

Same-Origin Examples

Base URL: https://example.com/page

SAME ORIGIN (✅ Allowed):
https://example.com/about          ← Same everything
https://example.com/api/users      ← Same everything
https://example.com:443/login      ← Port 443 is default for HTTPS

DIFFERENT ORIGIN (❌ Blocked without CORS):
http://example.com/page            ← Different protocol (http vs https)
https://api.example.com/page       ← Different subdomain
https://example.com:8080/page      ← Different port
https://another-site.com/page      ← Different domain

Why Same-Origin Policy?

// Scenario: You're logged into bank.com
// Evil site tries to access your data:

// evil-site.com page contains:
fetch('https://bank.com/api/account')
  .then(res => res.json())
  .then(data => {
    // Send victim's bank data to attacker
    sendToAttacker(data);
  });

// Without Same-Origin Policy:
// ❌ This would work! Evil site gets your bank data!

// With Same-Origin Policy:
// ✅ Browser blocks this request
// Error: "Blocked by CORS policy"

How CORS Works

CORS uses HTTP headers to allow servers to specify which origins can access their resources.

Simple CORS Request

For simple GET requests, the browser adds an Origin header:

# Browser sends:
GET /api/users HTTP/1.1
Host: api.example.com
Origin: https://myapp.com

# Server responds:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Content-Type: application/json

{"users": [...]}

# Browser checks: "Does Access-Control-Allow-Origin match my origin?"
# YES → Allow the response ✅
# NO → Block the response ❌

Preflight Request

For complex requests (POST, PUT, DELETE, custom headers), the browser sends a preflight request first:

# Step 1: Browser sends OPTIONS request (preflight)
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

# Step 2: Server responds with permissions
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

# Step 3: If allowed, browser sends actual request
POST /api/users HTTP/1.1
Host: api.example.com
Origin: https://myapp.com
Content-Type: application/json

{"name": "John Doe"}

CORS Flow Diagram

CLIENT (myapp.com)          BROWSER               SERVER (api.example.com)
       │                        │                            │
       │  fetch('api/users')    │                            │
       ├───────────────────────>│                            │
       │                        │                            │
       │                        │  OPTIONS /api/users        │
       │                        │  Origin: myapp.com         │
       │                        ├───────────────────────────>│
       │                        │                            │
       │                        │  Access-Control-Allow-     │
       │                        │  Origin: myapp.com         │
       │                        │<───────────────────────────┤
       │                        │                            │
       │                        │  GET /api/users            │
       │                        ├───────────────────────────>│
       │                        │                            │
       │                        │  Response + CORS headers   │
       │                        │<───────────────────────────┤
       │                        │                            │
       │  Response (if allowed) │                            │
       │<───────────────────────┤                            │

Key CORS Headers

Request Headers (sent by browser):

Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

Response Headers (sent by server):

# Who can access?
Access-Control-Allow-Origin: https://myapp.com
# or
Access-Control-Allow-Origin: *

# Which methods are allowed?
Access-Control-Allow-Methods: GET, POST, PUT, DELETE

# Which headers are allowed?
Access-Control-Allow-Headers: Content-Type, Authorization

# Can send cookies?
Access-Control-Allow-Credentials: true

# Cache preflight for how long? (seconds)
Access-Control-Max-Age: 86400

Common CORS Errors

Error 1: No 'Access-Control-Allow-Origin' Header

❌ ERROR:
Access to fetch at 'https://api.example.com/users' from origin 
'https://myapp.com' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the 
requested resource.

CAUSE: Server didn't send CORS headers
FIX: Add CORS headers on server

Error 2: Origin Not Allowed

❌ ERROR:
Access to fetch at 'https://api.example.com/users' from origin 
'https://myapp.com' has been blocked by CORS policy: 
The 'Access-Control-Allow-Origin' header has a value 
'https://other-site.com' that is not equal to the supplied origin.

CAUSE: Server allows other-site.com but not myapp.com
FIX: Add myapp.com to allowed origins on server

Error 3: Credentials Flag Mismatch

❌ ERROR:
Access to fetch at 'https://api.example.com/users' from origin 
'https://myapp.com' has been blocked by CORS policy: 
The value of the 'Access-Control-Allow-Origin' header in the 
response must not be the wildcard '*' when the request's 
credentials mode is 'include'.

CAUSE: Using credentials with wildcard (*)
FIX: Specify exact origin instead of *

Error 4: Method Not Allowed

❌ ERROR:
Access to fetch at 'https://api.example.com/users' from origin 
'https://myapp.com' has been blocked by CORS policy: 
Method PUT is not allowed by Access-Control-Allow-Methods 
in preflight response.

CAUSE: Server doesn't allow PUT method
FIX: Add PUT to Access-Control-Allow-Methods

Error 5: Header Not Allowed

❌ ERROR:
Access to fetch at 'https://api.example.com/users' from origin 
'https://myapp.com' has been blocked by CORS policy: 
Request header field authorization is not allowed by 
Access-Control-Allow-Headers in preflight response.

CAUSE: Server doesn't allow Authorization header
FIX: Add Authorization to Access-Control-Allow-Headers

Fixing CORS Issues

Solution 1: Enable CORS on Server

The proper solution is to configure CORS on the server.

Node.js (Express)

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

// Option 1: Enable CORS for all routes (simple)
app.use(cors());

// Option 2: Enable CORS for specific origin
app.use(cors({
  origin: 'https://myapp.com'
}));

// Option 3: Multiple origins
app.use(cors({
  origin: ['https://myapp.com', 'https://otherapp.com']
}));

// Option 4: Dynamic origin (function)
app.use(cors({
  origin: function(origin, callback) {
    const allowedOrigins = ['https://myapp.com', 'https://otherapp.com'];
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
}));

// Option 5: Full configuration
app.use(cors({
  origin: 'https://myapp.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
}));

app.listen(3000);

Manual CORS Headers (Node.js)

// Without cors package - manual headers
app.use((req, res, next) => {
  // Allow specific origin
  res.header('Access-Control-Allow-Origin', 'https://myapp.com');
  
  // Allow multiple origins
  const allowedOrigins = ['https://myapp.com', 'https://otherapp.com'];
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
  }
  
  // Allow methods
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  
  // Allow headers
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  
  // Allow credentials
  res.header('Access-Control-Allow-Credentials', 'true');
  
  // Handle preflight
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  
  next();
});

Solution 2: Development Proxy

For development only, use a proxy to avoid CORS:

Create React App

// package.json
{
  "name": "my-app",
  "proxy": "http://localhost:3001"
}

// Now requests go through proxy (same origin)
fetch('/api/users')  // Actually goes to http://localhost:3001/api/users

Vite

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3001',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\\/api/, '')
      }
    }
  }
}

Webpack Dev Server

// webpack.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3001',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  }
}

Solution 3: Browser Extension (Development Only)

Chrome Extension: "CORS Unblock" or "Allow CORS"

⚠️ Only for testing!
⚠️ Disables important security
⚠️ Never use in production
⚠️ Always fix CORS properly on server

Solution 4: JSONP (Legacy)

// JSONP (old technique, avoid if possible)
// Only works with GET requests
// Not secure, don't use for sensitive data

function jsonp(url, callback) {
  const script = document.createElement('script');
  script.src = \`\${url}?callback=\${callback}\`;
  document.head.appendChild(script);
}

// Server must respond with:
// callbackName({"data": "value"});

// Modern alternative: Use CORS instead!

CORS in Different Frameworks

Python (Flask)

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# Enable CORS for all routes
CORS(app)

# Or specific origin
CORS(app, origins=['https://myapp.com'])

# Or with options
CORS(app, 
     origins=['https://myapp.com'],
     methods=['GET', 'POST', 'PUT', 'DELETE'],
     allow_headers=['Content-Type', 'Authorization'])

@app.route('/api/users')
def get_users():
    return {'users': []}

Python (Django)

# Install: pip install django-cors-headers

# settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    ...
]

# Allow all origins (development)
CORS_ALLOW_ALL_ORIGINS = True

# Or specific origins (production)
CORS_ALLOWED_ORIGINS = [
    'https://myapp.com',
    'https://otherapp.com',
]

CORS_ALLOW_CREDENTIALS = True

CORS_ALLOW_METHODS = [
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
]

PHP

<?php
// Set CORS headers
header('Access-Control-Allow-Origin: https://myapp.com');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true');

// Handle preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit();
}

// Your API logic here
?>

Ruby on Rails

# Gemfile
gem 'rack-cors'

# config/application.rb
config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'https://myapp.com'
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options],
      credentials: true
  end
end

ASP.NET Core

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy("AllowMyApp",
            builder =>
            {
                builder.WithOrigins("https://myapp.com")
                       .AllowAnyMethod()
                       .AllowAnyHeader()
                       .AllowCredentials();
            });
    });
}

public void Configure(IApplicationBuilder app)
{
    app.UseCors("AllowMyApp");
    app.UseRouting();
    app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}

Nginx

server {
    listen 80;
    server_name api.example.com;

    location / {
        # Simple CORS
        add_header Access-Control-Allow-Origin https://myapp.com always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
        add_header Access-Control-Allow-Credentials true always;

        # Handle preflight
        if ($request_method = 'OPTIONS') {
            return 204;
        }

        # Proxy to backend
        proxy_pass http://localhost:3000;
    }
}

Apache (.htaccess)

# Enable CORS
Header set Access-Control-Allow-Origin "https://myapp.com"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
Header set Access-Control-Allow-Credentials "true"

# Handle preflight
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

Security Best Practices

1. Never Use Wildcard in Production with Credentials

// ❌ DANGEROUS - Anyone can access with credentials
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Credentials', 'true');

// ✅ SAFE - Specific origin with credentials
res.header('Access-Control-Allow-Origin', 'https://myapp.com');
res.header('Access-Control-Allow-Credentials', 'true');

2. Validate Origins

// ❌ BAD - Allows any origin
app.use(cors({
  origin: true  // Dangerous!
}));

// ✅ GOOD - Whitelist specific origins
const allowedOrigins = [
  'https://myapp.com',
  'https://app.mycompany.com'
];

app.use(cors({
  origin: function(origin, callback) {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
}));

3. Limit Methods and Headers

// ❌ BAD - Allow everything
app.use(cors({
  methods: '*',
  allowedHeaders: '*'
}));

// ✅ GOOD - Only what you need
app.use(cors({
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

4. Use Environment Variables

// .env
ALLOWED_ORIGINS=https://myapp.com,https://app.mycompany.com

// server.js
const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');

app.use(cors({
  origin: allowedOrigins,
  credentials: true
}));

5. Different Settings for Dev vs Production

// Development - Allow localhost
if (process.env.NODE_ENV === 'development') {
  app.use(cors({
    origin: ['http://localhost:3000', 'http://localhost:3001'],
    credentials: true
  }));
}

// Production - Strict origins
if (process.env.NODE_ENV === 'production') {
  app.use(cors({
    origin: ['https://myapp.com'],
    credentials: true
  }));
}

Common Misconceptions

Myth 1: "CORS is a Server Security Feature"

❌ WRONG: CORS protects the server
✅ RIGHT: CORS protects the USER

CORS is enforced by the BROWSER to protect users from 
malicious websites. The server still receives the request!

Myth 2: "I Can't Test APIs Without Fixing CORS"

❌ WRONG: Can't test until CORS is fixed
✅ RIGHT: Can test in many ways

Tools that bypass CORS (for testing):
✅ Postman
✅ curl
✅ Thunder Client (VS Code)
✅ HTTPie
✅ Server-to-server requests (no browser)

Myth 3: "Using a Proxy Fixes CORS"

❌ WRONG: Proxy fixes CORS permanently
✅ RIGHT: Proxy works only in development

Development proxy: ✅ Works (same origin)
Production: ❌ Still need to configure CORS on server

Myth 4: "CORS Prevents API Calls"

❌ WRONG: CORS blocks the API call
✅ RIGHT: CORS blocks reading the RESPONSE

The request DOES reach the server.
CORS only prevents JavaScript from reading the response.

Debugging CORS Issues

Step 1: Check Browser Console

Look for error messages like:
"Access to fetch... has been blocked by CORS policy"

Error will tell you exactly what's wrong:
- Missing header?
- Wrong origin?
- Method not allowed?
- Header not allowed?

Step 2: Check Network Tab

1. Open DevTools (F12)
2. Go to Network tab
3. Look for your request
4. Check Response Headers:
   - Access-Control-Allow-Origin: ?
   - Access-Control-Allow-Methods: ?
   - Access-Control-Allow-Headers: ?

5. If OPTIONS request (preflight):
   - Should return 200 or 204
   - Should have all Access-Control-* headers

Step 3: Test with Postman

If request works in Postman but fails in browser:
→ It's definitely a CORS issue
→ Server needs to send CORS headers

If request fails in both Postman and browser:
→ Not a CORS issue
→ Check server code, authentication, etc.

Step 4: Verify Server Configuration

// Add logging to see what's happening
app.use((req, res, next) => {
  console.log('Origin:', req.headers.origin);
  console.log('Method:', req.method);
  console.log('Headers:', req.headers);
  next();
});

Summary

CORS is a browser security feature that protects users by controlling which websites can access resources from other origins. While it can be confusing at first, understanding CORS is essential for modern web development.

Key Takeaways:

✅ CORS = Cross-Origin Resource Sharing
✅ Enforced by BROWSER, not server
✅ Protects USERS from malicious websites
✅ Same-Origin = same protocol + domain + port
✅ Server must send Access-Control-Allow-Origin header
✅ Complex requests trigger preflight (OPTIONS)
✅ Fix CORS on the SERVER, not client
✅ Never use * with credentials in production
✅ Test with Postman to verify it's a CORS issue
✅ Use proxy in development, proper CORS in production

Quick Fix Checklist:

  1. ✅ Add CORS middleware to server
  2. ✅ Specify allowed origins (not * in production)
  3. ✅ Allow needed methods and headers
  4. ✅ Handle OPTIONS requests (preflight)
  5. ✅ Test from browser and Postman