CORS - Cross Origin Request Sharing
Thilan Dissanayaka Computer Networking May 05, 2020

CORS - Cross Origin Request Sharing

Cross-Origin Resource Sharing (CORS) is a crucial web security mechanism that controls how web pages from one domain can access resources from another domain. Understanding CORS is essential for modern web development, as it directly impacts how APIs, fonts, images, and other resources can be shared across different origins.

What is CORS?

CORS is a browser security feature that implements the same-origin policy with controlled exceptions. The same-origin policy restricts web pages from making requests to a different domain, protocol, or port than the one serving the web page. CORS provides a way to relax this restriction in a controlled manner.

An "origin" consists of three parts:

  • Protocol (http or https)
  • Domain (example.com)
  • Port (80, 443, 3000, etc.)

Two URLs are considered to have the same origin only if all three components match exactly.

Why CORS Exists

Without CORS, malicious websites could easily make requests to other sites on behalf of users, potentially accessing sensitive information or performing unauthorized actions. CORS ensures that cross-origin requests are only allowed when explicitly permitted by the target server.

How CORS Works

When a web application makes a cross-origin request, the browser adds special CORS headers to the request and checks the response headers to determine if the request should be allowed.

Simple Requests

For simple requests (GET, POST with certain content types), the browser sends the request directly with an Origin header:

GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://myapp.com

The server responds with CORS headers indicating whether the request is allowed:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Content-Type: application/json

Preflight Requests

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

OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://myapp.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

The server responds with allowed methods and headers:

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

Common CORS Headers

Response Headers (Server to Browser)

Access-Control-Allow-Origin Specifies which origins can access the resource:

Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Origin: *  # Allow all origins (use with caution)

Access-Control-Allow-Methods Lists allowed HTTP methods:

Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

Access-Control-Allow-Headers Specifies which headers can be used in requests:

Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With

Access-Control-Allow-Credentials Indicates whether credentials (cookies, authorization headers) can be included:

Access-Control-Allow-Credentials: true

Access-Control-Max-Age Specifies how long preflight results can be cached:

Access-Control-Max-Age: 3600  # Cache for 1 hour

Request Headers (Browser to Server)

Origin Indicates the origin of the request:

Origin: https://myapp.com

Access-Control-Request-Method Used in preflight requests to indicate the intended method:

Access-Control-Request-Method: PUT

Access-Control-Request-Headers Lists headers that will be used in the actual request:

Access-Control-Request-Headers: Content-Type, Authorization

Practical Examples

Example 1: Basic API Request

Consider a frontend application at https://myapp.com trying to fetch data from https://api.example.com:

// Frontend JavaScript (running on https://myapp.com)
fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('CORS error:', error));

For this to work, the API server must include appropriate CORS headers:

// Server-side (Node.js/Express example)
app.get('/users', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'https://myapp.com');
  res.header('Access-Control-Allow-Methods', 'GET');
  res.json([{ id: 1, name: 'John Doe' }]);
});

Example 2: POST Request with Custom Headers

// Frontend making a POST request with authentication
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({ name: 'Jane Doe', email: '[email protected]' })
});

The server needs to handle both the preflight OPTIONS request and the actual POST:

// Handle preflight request
app.options('/users', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'https://myapp.com');
  res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.sendStatus(200);
});

// Handle actual POST request
app.post('/users', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'https://myapp.com');
  res.header('Access-Control-Allow-Credentials', 'true');
  // Process the request...
  res.json({ id: 2, name: 'Jane Doe' });
});

Example 3: Using Express CORS Middleware

Instead of manually setting headers, you can use the CORS middleware:

const cors = require('cors');

// Allow all origins (development only)
app.use(cors());

// Or configure specific origins
const corsOptions = {
  origin: ['https://myapp.com', 'https://admin.myapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
};

app.use(cors(corsOptions));

Common CORS Issues and Solutions

Issue 1: "Access-Control-Allow-Origin" Error

Problem: Browser blocks request due to missing or incorrect CORS headers.

Solution: Ensure your server includes the correct Access-Control-Allow-Origin header:

// Wrong - will cause CORS errors
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
// When the actual origin is https://localhost:3000

// Correct
res.header('Access-Control-Allow-Origin', 'https://localhost:3000');

Issue 2: Credentials Not Allowed

Problem: Cookies or authentication headers are not being sent.

Solution: Set both client and server to handle credentials:

// Client-side
fetch('https://api.example.com/protected', {
  credentials: 'include'  // Include cookies
});

// Server-side
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Origin', 'https://myapp.com'); // Cannot use '*' with credentials

Issue 3: Preflight Request Failing

Problem: Complex requests fail because preflight OPTIONS request isn’t handled.

Solution: Implement proper OPTIONS handling:

app.use((req, res, next) => {
  if (req.method === 'OPTIONS') {
    res.header('Access-Control-Allow-Origin', 'https://myapp.com');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    return res.sendStatus(200);
  }
  next();
});

Best Practices

1. Be Specific with Origins

Avoid using * for Access-Control-Allow-Origin in production, especially when credentials are involved:

// Good
res.header('Access-Control-Allow-Origin', 'https://trustedapp.com');

// Avoid in production
res.header('Access-Control-Allow-Origin', '*');

2. Minimize Exposed Headers

Only allow headers that are actually needed:

// Only include necessary headers
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

3. Use Environment-Based Configuration

const allowedOrigins = process.env.NODE_ENV === 'production' 
  ? ['https://myapp.com', 'https://admin.myapp.com']
  : ['http://localhost:3000', 'http://localhost:3001'];

const corsOptions = {
  origin: allowedOrigins,
  credentials: true
};

4. Cache Preflight Responses

Set appropriate Access-Control-Max-Age to reduce preflight requests:

res.header('Access-Control-Max-Age', '86400'); // 24 hours

Debugging CORS Issues

When encountering CORS problems:

  1. Check the browser console for specific error messages
  2. Inspect network requests to see what headers are being sent and received
  3. Verify the exact origin (including protocol and port)
  4. Test with simple requests first before adding complexity
  5. Use browser developer tools to examine preflight requests

Security Considerations

CORS is a security feature, not a security vulnerability. However, misconfigurations can create security risks:

  • Never use Access-Control-Allow-Origin: * with credentials
  • Validate and sanitize all incoming data regardless of CORS settings
  • Don’t rely on CORS for sensitive operations - implement proper authentication and authorization
  • Regularly audit your CORS configuration to ensure it matches your security requirements

Conclusion

CORS is an essential mechanism for enabling secure cross-origin requests in modern web applications. By understanding how CORS works and implementing it correctly, you can build applications that safely share resources across different domains while maintaining security. Remember to always configure CORS headers specifically for your use case rather than using overly permissive settings that could compromise security.

ALSO READ
Blockchain 0x000 – Understanding the Fundamentals
May 21, 2020 Web3 Development

Imagine a world where strangers can exchange money, share data, or execute agreements without ever needing to trust a central authority. No banks, no intermediaries, no single point of failure yet...

Identity and Access Management (IAM)
May 11, 2020 Identity & Access Management

Who are you — and what are you allowed to do? That's the fundamental question every secure system must answer. And it's exactly what Identity and Access Management (IAM) is built to solve.

How I built a web based CPU Simulator
May 07, 2020 Pet Projects

As someone passionate about computer engineering, reverse engineering, and system internals, I've always been fascinated by what happens "under the hood" of a computer. This curiosity led me to...

Writing a Shell Code for Linux
Apr 21, 2020 Exploit Development

Shellcode is a small piece of machine code used as the payload in exploit development. In this post, we write Linux shellcode from scratch — starting with a simple exit, building up to spawning a shell, and explaining every decision along the way.

Exploiting a Stack Buffer Overflow on Windows
Apr 12, 2020 Exploit Development

In a previous tutorial we discusses how we can exploit a buffer overflow vulnerability on a Linux machine. I wen through all theories in depth and explained each step. Now today we are going to jump...

Access Control Models
Apr 08, 2020 Identity & Access Management

Access control is one of the most fundamental concepts in security. Every time you set file permissions, assign user roles, or restrict access to a resource, you're implementing some form of access control. But not all access control is created equal...

Exploiting a  Stack Buffer Overflow  on Linux
Apr 01, 2020 Exploit Development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening....

Basic concepts of Cryptography
Mar 01, 2020 Cryptography

Ever notice that little padlock icon in your browser's address bar? That's cryptography working silently in the background, protecting everything you do online. Whether you're sending an email,...

Common Web Application Attacks
Feb 05, 2020 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these...

Remote Code Execution (RCE)
Jan 02, 2020 Application Security

Remote Code Execution (RCE) is the holy grail of application security vulnerabilities. It allows an attacker to execute arbitrary code on a remote server — and the consequences are as bad as it sounds. In this post, we'll go deep into RCE across multiple languages, including PHP, Java, Python, and Node.js.