DevToolBoxGRÁTIS
Blog

Como corrigir erros CORS: Guia completo

12 min de leituraby DevToolBox

CORS errors are one of the most frustrating issues in web development. You see the dreaded red console message, your API call fails, and nothing seems to work. This guide will help you understand exactly what CORS is, diagnose your specific error, and fix it with the right solution for your stack.

What is CORS? (30-Second Explanation)

Cross-Origin Resource Sharing (CORS) is a browser security feature that blocks web pages from making requests to a different domain than the one serving the page. When your frontend at http://localhost:3000 tries to fetch data from http://api.example.com, the browser checks if the API server explicitly allows this cross-origin request via special HTTP headers.

How CORS Works: The Flow

┌─────────────────┐         ┌─────────────────────┐
│   Browser        │         │   API Server         │
│   (Frontend)     │         │   (Backend)          │
│                  │         │                      │
│ http://localhost │ ──────> │ https://api.example  │
│     :3000        │  fetch  │     .com             │
│                  │         │                      │
└─────────────────┘         └─────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    │                               │
              CORS Headers                   No CORS Headers
              Present?                       Present?
                    │                               │
                    ▼                               ▼
          ┌─────────────────┐            ┌──────────────────┐
          │  Response sent  │            │  Browser BLOCKS  │
          │  to JavaScript  │            │  the response    │
          └─────────────────┘            └──────────────────┘

When CORS is properly configured, the server responds with headers that tell the browser: "Yes, I allow requests from this origin."

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json

{"data": "success"}

When CORS is NOT configured, the browser blocks the response (even though the server may have processed the request successfully).

HTTP/1.1 200 OK
Content-Type: application/json
# No Access-Control-Allow-Origin header!

{"data": "success"}  <-- Browser received this but BLOCKS JavaScript from reading it

5 Most Common CORS Error Messages

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

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

Meaning: The server did not include the Access-Control-Allow-Origin header in its response. This is the most common CORS error and means the server has no CORS configuration at all.

Error 2: Wildcard with Credentials

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' 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'.

Meaning: You are sending cookies or authorization headers (credentials: "include"), but the server responds with Access-Control-Allow-Origin: *. When credentials are involved, the server must specify the exact origin, not a wildcard.

Error 3: Preflight Request Failed

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Meaning: Before your actual request, the browser sent an OPTIONS request (preflight) to check permissions. The server either did not handle the OPTIONS method or did not include CORS headers in the preflight response.

Error 4: Method Not Allowed

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

Meaning: The server's Access-Control-Allow-Methods header does not include the HTTP method you are using (e.g., PUT, DELETE, PATCH). The server needs to explicitly allow these methods.

Error 5: Header Not Allowed

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers in preflight response.

Meaning: You are sending a custom header (like X-Custom-Header or Authorization) that the server has not explicitly allowed in its Access-Control-Allow-Headers response header.

Diagnosis Flowchart: Where Is the Problem?

Use this decision tree to quickly identify whether your CORS issue is a frontend problem, a backend problem, or a proxy configuration problem:

Step 1: Open browser DevTools > Network tab. Do you see an OPTIONS request before your actual request?

YES -> The browser is sending a preflight request. Check if the OPTIONS response has status 200 and includes CORS headers. If not, your backend is not handling OPTIONS requests.

NO -> This is a simple request (GET/POST with standard headers). The problem is that the server response is missing CORS headers.

Step 2: Does the error mention "preflight"?

YES -> Backend problem. Your server needs to handle OPTIONS requests and return proper CORS headers.

NO -> Check if you are using credentials (cookies, Authorization header). If yes, you cannot use wildcard (*) for Access-Control-Allow-Origin.

Step 3: Does the API work when called directly (e.g., via curl or Postman)?

YES -> This confirms it is a CORS issue (browser-only). The server works fine but is missing CORS headers.

NO -> This is NOT a CORS issue. The API itself has a problem (auth, routing, server error).

Summary: 90% of CORS errors are solved by configuring the correct headers on your backend server.

Server-Side Fixes by Framework

Express.js (Node.js)

// Method 1: Using the cors package (recommended)
npm install cors

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

// Allow specific origins
app.use(cors({
  origin: ['http://localhost:3000', 'https://yoursite.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400 // Cache preflight for 24 hours
}));

// Method 2: Manual middleware
app.use((req, res, next) => {
  const allowedOrigins = ['http://localhost:3000', 'https://yoursite.com'];
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');

  // Handle preflight
  if (req.method === 'OPTIONS') {
    res.setHeader('Access-Control-Max-Age', '86400');
    return res.status(204).end();
  }
  next();
});

Django (Python)

# Install django-cors-headers
pip install django-cors-headers

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

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # Must be FIRST
    'django.middleware.common.CommonMiddleware',
    ...
]

# Option 1: Allow specific origins
CORS_ALLOWED_ORIGINS = [
    'http://localhost:3000',
    'https://yoursite.com',
]

# Option 2: Allow all origins (dev only!)
# CORS_ALLOW_ALL_ORIGINS = True

# Allow credentials (cookies, auth headers)
CORS_ALLOW_CREDENTIALS = True

# Allowed headers
CORS_ALLOW_HEADERS = [
    'content-type',
    'authorization',
    'x-requested-with',
]

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

Flask (Python)

# Install flask-cors
pip install flask-cors

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# Option 1: Allow specific origins
CORS(app, origins=['http://localhost:3000', 'https://yoursite.com'],
     supports_credentials=True,
     allow_headers=['Content-Type', 'Authorization'],
     methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])

# Option 2: Manual approach
@app.after_request
def after_request(response):
    origin = request.headers.get('Origin')
    allowed = ['http://localhost:3000', 'https://yoursite.com']
    if origin in allowed:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
        response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response

Go (net/http)

package main

import (
    "net/http"
    "strings"
)

func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        allowedOrigins := map[string]bool{
            "http://localhost:3000": true,
            "https://yoursite.com": true,
        }

        origin := r.Header.Get("Origin")
        if allowedOrigins[origin] {
            w.Header().Set("Access-Control-Allow-Origin", origin)
            w.Header().Set("Access-Control-Allow-Methods",
                strings.Join([]string{"GET","POST","PUT","DELETE","PATCH"}, ","))
            w.Header().Set("Access-Control-Allow-Headers",
                "Content-Type, Authorization")
            w.Header().Set("Access-Control-Allow-Credentials", "true")
            w.Header().Set("Access-Control-Max-Age", "86400")
        }

        // Handle preflight
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }

        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/data", handleData)

    handler := corsMiddleware(mux)
    http.ListenAndServe(":8080", handler)
}

Nginx Configuration

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

    # CORS headers for all locations
    set $cors_origin "";
    if ($http_origin ~* "^https?://(localhost:3000|yoursite\.com)$") {
        set $cors_origin $http_origin;
    }

    location /api/ {
        # CORS headers
        add_header 'Access-Control-Allow-Origin' $cors_origin always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Max-Age' 86400 always;

        # Handle preflight requests
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' $cors_origin always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Access-Control-Max-Age' 86400;
            add_header 'Content-Length' 0;
            add_header 'Content-Type' 'text/plain';
            return 204;
        }

        proxy_pass http://backend:8080;
    }
}

Reverse Proxy as CORS Middleware

If you cannot modify the backend API (e.g., third-party API), use a reverse proxy to add CORS headers. This is the most reliable production solution.

Nginx Reverse Proxy

# Nginx as CORS proxy for a third-party API
server {
    listen 80;
    server_name cors-proxy.yoursite.com;

    location /proxy/ {
        # Add CORS headers
        add_header 'Access-Control-Allow-Origin' 'https://yoursite.com' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;

        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'https://yoursite.com';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
            add_header 'Access-Control-Max-Age' 86400;
            return 204;
        }

        # Strip /proxy/ prefix and forward to third-party API
        rewrite ^/proxy/(.*) /$1 break;
        proxy_pass https://third-party-api.com;
        proxy_set_header Host third-party-api.com;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# Usage: fetch('https://cors-proxy.yoursite.com/proxy/endpoint')

Apache Reverse Proxy

# Apache as CORS proxy
<VirtualHost *:80>
    ServerName cors-proxy.yoursite.com

    # Enable required modules
    # a2enmod proxy proxy_http headers rewrite

    <Location "/proxy/">
        Header always set Access-Control-Allow-Origin "https://yoursite.com"
        Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
        Header always set Access-Control-Allow-Headers "Content-Type, Authorization"
        Header always set Access-Control-Allow-Credentials "true"

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

        # Proxy to backend
        ProxyPass "http://backend:8080/"
        ProxyPassReverse "http://backend:8080/"
    </Location>
</VirtualHost>

Development-Only Solutions

These solutions are for local development only. Never use these in production.

Vite Proxy

Vite's built-in proxy forwards requests from your dev server to the API, avoiding CORS entirely because the browser sees only same-origin requests:

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    proxy: {
      // Proxy /api requests to backend
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        // Optional: rewrite path
        // rewrite: (path) => path.replace(/^\/api/, ''),
      },
      // Proxy with WebSocket support
      '/ws': {
        target: 'ws://localhost:8080',
        ws: true,
      },
    },
  },
});

// In your code, use relative URLs:
// fetch('/api/users')  instead of  fetch('http://localhost:8080/api/users')

Webpack devServer Proxy

Create React App and other webpack-based setups support a similar proxy:

// package.json (Create React App)
{
  "proxy": "http://localhost:8080"
}

// Or for more control, create src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:8080',
      changeOrigin: true,
    })
  );
};

// webpack.config.js (manual setup)
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      },
    },
  },
};

Next.js Rewrites

Next.js can proxy API requests through its own server using rewrites:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:8080/api/:path*',
      },
    ];
  },
};

module.exports = nextConfig;

// In your code:
// fetch('/api/users')  -- proxied to http://localhost:8080/api/users

Common Pitfalls and Edge Cases

Pitfall 1: Wildcard (*) with Credentials

When your frontend sends credentials (cookies, Authorization header), the server CANNOT use Access-Control-Allow-Origin: *. It must echo back the exact origin:

WRONG (will fail with credentials):

// Frontend
fetch('https://api.example.com/data', {
  credentials: 'include'  // Sending cookies
});

// Backend response header:
Access-Control-Allow-Origin: *   // FAILS! Cannot use * with credentials

CORRECT (dynamic origin for credentials):

// Backend (Express.js example)
app.use((req, res, next) => {
  // Echo back the exact origin
  const origin = req.headers.origin;
  const allowedOrigins = ['http://localhost:3000', 'https://yoursite.com'];

  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);  // Exact origin
  }
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  next();
});

Pitfall 2: Missing OPTIONS Handler (Preflight)

Non-simple requests trigger a preflight OPTIONS request. If your server returns 404 or 405 for OPTIONS, CORS fails before your actual request is even sent. Common triggers for preflight:

TriggerExample
Custom headersAuthorization, X-Request-ID, X-API-Key
Non-simple methodsPUT, DELETE, PATCH
Non-simple Content-Typeapplication/json, application/xml
ReadableStream bodyStreaming request body
// Ensure your server handles OPTIONS for all API routes:
app.options('*', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
  res.setHeader('Access-Control-Max-Age', '86400');
  res.status(204).end();
});

Pitfall 3: Cookies and SameSite

Cross-origin cookies require three things to work together:

// 1. Frontend: include credentials
fetch('https://api.example.com/data', {
  credentials: 'include'
});

// 2. Backend: allow credentials + exact origin
res.setHeader('Access-Control-Allow-Origin', 'https://yoursite.com');  // NOT *
res.setHeader('Access-Control-Allow-Credentials', 'true');

// 3. Cookie must have correct attributes
Set-Cookie: session=abc123; SameSite=None; Secure; HttpOnly; Path=/

// IMPORTANT: SameSite=None requires Secure (HTTPS).
// This means cross-origin cookies do NOT work on HTTP (localhost).
// For local dev, use a proxy instead of cross-origin cookies.

Pitfall 4: Caching Preflight Responses

Preflight (OPTIONS) requests can be slow. Use Access-Control-Max-Age to cache them:

// Server response header:
Access-Control-Max-Age: 86400  // Cache preflight for 24 hours

// Without this header, the browser sends a preflight OPTIONS
// request before EVERY non-simple request, adding latency.

// With caching, the browser remembers the CORS permissions
// and skips the preflight for subsequent requests.

Note: Chrome caps this at 7200 seconds (2 hours). Firefox caps at 86400 (24 hours).

Pitfall 5: CORS on Redirects

If your API endpoint redirects (301/302) to another URL, the CORS headers must be present on BOTH the original response AND the redirected response. Many developers miss this. Avoid redirects in API endpoints when possible.

// PROBLEM: API endpoint redirects
// GET https://api.example.com/users -> 301 -> https://api.example.com/v2/users
// CORS headers must be on BOTH responses!

// SOLUTION: Avoid redirects in API endpoints
// Option 1: Update the frontend URL
fetch('https://api.example.com/v2/users');  // Use final URL directly

// Option 2: Make the redirect include CORS headers
// (requires server configuration on both the old and new endpoint)

Quick Reference: CORS Headers

HeaderPurposeExample Value
Access-Control-Allow-OriginWhich origins can access the resourcehttps://yoursite.com or *
Access-Control-Allow-MethodsWhich HTTP methods are allowedGET, POST, PUT, DELETE
Access-Control-Allow-HeadersWhich request headers are allowedContent-Type, Authorization
Access-Control-Allow-CredentialsWhether cookies/auth can be senttrue
Access-Control-Max-AgeHow long to cache preflight (seconds)86400
Access-Control-Expose-HeadersWhich response headers JS can readX-Total-Count, X-Request-ID

Frequently Asked Questions

Can I disable CORS in the browser?

Technically yes, by launching Chrome with --disable-web-security flag, but this is extremely dangerous and should never be used for anything other than quick debugging. It disables all browser security policies. For development, use a proxy (Vite, webpack) instead.

Why does my API work in Postman but not in the browser?

CORS is a browser-only security mechanism. Tools like Postman, curl, and server-to-server requests do not enforce CORS. The browser checks for CORS headers because it runs untrusted code (JavaScript from websites) and needs to protect users from malicious cross-origin requests.

Is CORS a frontend or backend fix?

CORS is almost always a backend fix. The server must send the correct Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers response headers. The frontend cannot bypass CORS (by design). The only frontend workaround is to use a proxy so the request appears same-origin.

Should I use Access-Control-Allow-Origin: * in production?

Only for truly public APIs that do not use cookies or authentication. For any API that requires credentials, you must specify exact origins. Using * with credentials will always fail. For most applications, whitelist specific origins dynamically based on the request Origin header.

What is a preflight request and why does it happen?

A preflight request is an automatic OPTIONS request sent by the browser before the actual request. It happens when the request uses custom headers, non-simple HTTP methods (PUT, DELETE, PATCH), or a Content-Type other than form-encoded/multipart/text-plain. The preflight checks if the server allows the actual request. You must handle OPTIONS on your server.

How do I fix CORS for WebSocket connections?

WebSocket connections (ws:// or wss://) do not follow CORS rules. The initial HTTP upgrade handshake includes an Origin header, but the server must validate it manually. Most WebSocket libraries (Socket.IO, ws) have an origin option to whitelist allowed origins. If you see a CORS error with WebSockets, the issue is usually with the initial HTTP handshake endpoint, not the WebSocket itself.

𝕏 Twitterin LinkedIn
Isso foi útil?

Fique atualizado

Receba dicas de dev e novos ferramentas semanalmente.

Sem spam. Cancele a qualquer momento.

Try These Related Tools

🛡️CSP Header GeneratorNXNginx Config Generator.ht.htaccess Generator4xxHTTP Status Code Reference

Related Articles

REST API Melhores Práticas: O Guia Completo para 2026

Aprenda as melhores práticas de design REST API: convenções de nomes, tratamento de erros, autenticação e segurança.

Content Security Policy (CSP) Guia Completo: Do Básico à Produção

Aprenda CSP do zero: todas as diretivas, configurações comuns, reporting e deploy.