Skip to main content

đźš§ CORS

A club's guest list for websites

The Nightclub Bouncer Analogy

A nightclub has a bouncer:

  • VIP list: These people can enter
  • Not on list: "Sorry, you can't come in"

The bouncer checks before letting anyone in.

CORS is a bouncer for your API. It decides which websites can access your server's data.


The Problem CORS Solves

Same-Origin Policy

Browsers enforce "Same-Origin Policy":
  Scripts on site A can only access data from site A
  Scripts on site A CANNOT access data from site B

Origin = protocol + domain + port
  https://example.com:443

Why This Matters

Without protection:
  You visit malicious-site.com
  Malicious script: "Fetch user's bank data from bank.com"
  Your cookies sent! Bank data stolen!

With Same-Origin Policy:
  Browser blocks the request
  "Not same origin, can't access"

But Sometimes You NEED Cross-Origin

API at:       api.mycompany.com
Frontend at:  app.mycompany.com

Different origins! Blocked by default.
Enter CORS: "I'll allow app.mycompany.com"

How CORS Works

The Headers

Request from app.mycompany.com to api.mycompany.com:

Server checks origin, responds with:
  Access-Control-Allow-Origin: https://app.mycompany.com

Browser sees header:
  "Origin is allowed, let the script access the response"

Simple vs Preflight Requests

Simple Requests

Conditions (all must be true):
  - GET, HEAD, or POST
  - Standard headers only
  - No custom headers

Flow:
  Browser → Request → Server
  Server → Response with CORS headers
  Browser → Allow or block based on headers

Preflight Requests

When request has:
  - PUT, DELETE, PATCH methods
  - Custom headers
  - Non-standard content types

Flow:
  Browser → OPTIONS request (preflight)
  Server → "Yes, allowed" or "No, blocked"
  Browser → Actual request (if allowed)

Preflight Flow

1. Browser: OPTIONS /api/users
   Origin: https://app.mycompany.com
   Access-Control-Request-Method: DELETE
   Access-Control-Request-Headers: X-Custom-Header

2. Server: 200 OK
   Access-Control-Allow-Origin: https://app.mycompany.com
   Access-Control-Allow-Methods: GET, POST, DELETE
   Access-Control-Allow-Headers: X-Custom-Header

3. Browser: DELETE /api/users
   (Actual request)

CORS Headers Explained

Response Headers

HeaderPurpose
Access-Control-Allow-OriginWhich origins can access
Access-Control-Allow-MethodsWhich HTTP methods allowed
Access-Control-Allow-HeadersWhich headers allowed
Access-Control-Allow-CredentialsAllow cookies/auth?
Access-Control-Expose-HeadersHeaders script can read
Access-Control-Max-AgeCache preflight for X seconds

Example Configuration

Allow specific origin:
  Access-Control-Allow-Origin: https://myapp.com

Allow multiple methods:
  Access-Control-Allow-Methods: GET, POST, PUT, DELETE

Allow credentials (cookies):
  Access-Control-Allow-Credentials: true
  (Note: Cannot use * with credentials!)

Cache preflight:
  Access-Control-Max-Age: <seconds> (cache for a while)

Common Configurations

Allow Single Origin

Access-Control-Allow-Origin: https://app.mycompany.com

Most restrictive. Only this origin can access.

Allow Multiple Origins (Dynamic)

Check request Origin header
If origin is in your allowed list:
  Set Access-Control-Allow-Origin to that origin
Else:
  Block or omit header

You check server-side, respond dynamically.

Allow All Origins (Dangerous!)

Access-Control-Allow-Origin: *

Anyone can access your API!
Cannot use with credentials.
OK for public data only.

CORS with Credentials

Sending Cookies Cross-Origin

Client must:
  fetch(url, { credentials: 'include' })

Server must:
  Access-Control-Allow-Credentials: true
  Access-Control-Allow-Origin: https://specific-origin.com
  (Cannot be *)

Both sides must opt in!

Common CORS Errors

"No 'Access-Control-Allow-Origin' header"

Cause: Server didn't send CORS headers
Fix: Configure server to send appropriate headers

"Wildcard not allowed with credentials"

Cause: Using * with credentials: true
Fix: Use specific origin, not wildcard

"Preflight response didn't succeed"

Cause: Server returned error for OPTIONS request
Fix: Ensure server handles OPTIONS and returns 200

Security Considerations

Don't Use * with Sensitive Data

Access-Control-Allow-Origin: *

Any website can read your API responses!
Only for truly public APIs.

Validate Origins Server-Side

Don't trust the Origin header blindly.
Maintain an explicit allowlist.
Check against it before responding.

Be Careful with Credentials

Access-Control-Allow-Credentials: true

Now cookies are sent cross-origin.
Make sure you really need this.

Common Mistakes

1. Thinking CORS Is Backend Security

CORS is a BROWSER feature.
Browsers enforce it.
Attackers with curl ignore it completely.

CORS protects users, not your server.
Use authentication for server security.

2. Reflecting Any Origin

Bad: Whatever Origin is sent, allow it

If (origin exists) {
  Allow-Origin: origin
}

This is the same as allowing everything!
Validate the origin against a list.

3. Not Handling OPTIONS

DELETE request → Browser sends OPTIONS first
Server returns 404 for OPTIONS → CORS fails

Handle OPTIONS method explicitly.

FAQ

Q: Why doesn't my API work from the browser but works from Postman?

Postman isn't a browser - it doesn't enforce CORS. Browsers do.

Q: Can I disable CORS?

In development, you can use browser extensions or proxy. In production, configure your server properly.

Q: Does CORS protect my API?

No! CORS protects browsers from malicious scripts. Anyone can still call your API directly. Use authentication.

Q: Why is preflight needed?

To check permissions before making changes. DELETE shouldn't happen if not allowed.


Summary

CORS controls which websites can access your API from browsers, protecting users from malicious cross-origin requests.

Key Takeaways:

  • Browsers enforce Same-Origin Policy
  • CORS allows controlled exceptions
  • Simple requests: one request
  • Complex requests: preflight first
  • Use specific origins, not *
  • CORS is browser protection, not API security

CORS is the bouncer that helps protect users in the browser context.

Leave a Comment

Comments (0)

Be the first to comment on this concept.

Comments are approved automatically.