Visión General
Cross-Origin Resource Sharing (CORS) es un mecanismo de seguridad del navegador que controla qué orígenes pueden acceder a los recursos de tu API. Un CORS mal configurado es una de las fuentes más comunes de fricción en la integración frontend-backend y de vulnerabilidades de seguridad. La solucion a continuacion cubre la implementación de middleware CORS apropiado con validación de allowlist, manejo de preflight, soporte de credenciales y declaraciones explícitas de headers/métodos en Python, JavaScript y Java.
Cuándo Usar
Usa este recurso cuando:
- Tu frontend (SPA, app móvil, widget de terceros) corre en un origen distinto al de tu API
- Necesites soportar requests cross-origin autenticados con cookies o headers de autorización
- Estés construyendo una API pública consumida por múltiples dominios externos
- Estés debuggeando misteriosos errores de navegador “CORS policy” en llamadas a APIs
Solución
Python (Flask)
from flask import Flask, request, make_response
from urllib.parse import urlparse
app = Flask(__name__)
ALLOWED_ORIGINS = {
"https://app.example.com",
"https://admin.example.com",
"http://localhost:3000",
}
ALLOWED_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"]
ALLOWED_HEADERS = ["Content-Type", "Authorization", "X-Request-ID"]
ALLOW_CREDENTIALS = True
@app.after_request
def add_cors_headers(response):
origin = request.headers.get("Origin")
# Solo refleja orígenes permitidos; nunca uses "*" con credenciales
if origin in ALLOWED_ORIGINS:
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Vary"] = "Origin"
if ALLOW_CREDENTIALS:
response.headers["Access-Control-Allow-Credentials"] = "true"
return response
@app.route("/api/<path:path>", methods=["OPTIONS"])
def handle_preflight(path):
origin = request.headers.get("Origin")
if origin not in ALLOWED_ORIGINS:
return make_response(("", 204)) # Sin headers CORS para orígenes no permitidos
response = make_response(("", 204))
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Methods"] = ", ".join(ALLOWED_METHODS)
response.headers["Access-Control-Allow-Headers"] = ", ".join(ALLOWED_HEADERS)
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Max-Age"] = "86400"
return response
JavaScript (Express)
import express from "express";
const app = express();
const ALLOWED_ORIGINS = new Set([
"https://app.example.com",
"https://admin.example.com",
"http://localhost:3000",
]);
function corsMiddleware(req, res, next) {
const origin = req.headers.origin;
if (origin && ALLOWED_ORIGINS.has(origin)) {
res.header("Access-Control-Allow-Origin", origin);
res.header("Vary", "Origin");
}
res.header("Access-Control-Allow-Credentials", "true");
// Request de preflight
if (req.method === "OPTIONS") {
if (origin && !ALLOWED_ORIGINS.has(origin)) {
return res.sendStatus(204);
}
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH");
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Request-ID");
res.header("Access-Control-Max-Age", "86400");
return res.sendStatus(204);
}
next();
}
app.use(corsMiddleware);
app.use(express.json());
// Alternativa: usando el paquete cors con allowlist explícito
// import cors from "cors";
// app.use(cors({
// origin: (origin, callback) => {
// if (!origin || ALLOWED_ORIGINS.has(origin)) {
// callback(null, true);
// } else {
// callback(new Error("Not allowed by CORS"));
// }
// },
// credentials: true,
// methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
// allowedHeaders: ["Content-Type", "Authorization", "X-Request-ID"],
// }));
Java (Spring Boot)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig {
private static final String[] ALLOWED_ORIGINS = {
"https://app.example.com",
"https://admin.example.com",
"http://localhost:3000"
};
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(ALLOWED_ORIGINS)
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
.allowedHeaders("Content-Type", "Authorization", "X-Request-ID")
.allowCredentials(true)
.maxAge(86400);
}
};
}
}
// Integración con Spring Security (si usas SecurityFilterChain)
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(cors -> {})
.csrf(csrf -> csrf.disable()) // solo si la API es stateless con tokens
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll()
);
// Consulta [Checklist de Seguridad de APIs](/guides/api-security-checklist-guide/) para patrones de autenticación.
return http.build();
}
}
Explicación
- Same-Origin Policy los navegadores bloquean requests de
origin-a.comaorigin-b.compor defecto. CORS es una relajación controlada de esta política. - Preflight (OPTIONS) los navegadores envían un request de preflight para métodos no simples (PUT, DELETE, PATCH) y headers custom. El servidor debe responder con orígenes, métodos y headers permitidos.
Access-Control-Allow-Origindebe ser una coincidencia exacta (https://app.example.com) o*. Nunca uses*cuandoAccess-Control-Allow-Credentials: trueestá seteado — los navegadores rechazan esta combinación.Vary: Origines crítico cuando sirves headers CORS distintos según el origen del request. Sin él, los CDNs pueden cachear una respuesta con un header de origen y servirla a requests de orígenes diferentes.Access-Control-Allow-Credentialshabilita cookies y headers de autorización en requests cross-origin. Tanto el cliente (withCredentials: true/credentials: 'include') como el servidor deben optar por esto.
Variantes
| Enfoque | Configuración | Ideal Para |
|---|---|---|
| Allowlist | Lista explícita de orígenes | APIs de producción con consumidores conocidos |
| Patrón regex | *.example.com | Wildcards de subdominios (valida cuidadosamente) |
| Origen en vivo | Origen validado en runtime | APIs multi-tenant con orígenes por tenant |
Wildcard * | Sin restricción de origen | APIs públicas de solo lectura sin credenciales |
| Proxy | Frontend proxy a API | Desarrollo, deployment de mismo origen |
Lo que funciona
- Nunca uses
*con credenciales — los navegadores rechazanAccess-Control-Allow-Origin: *cuandoAllow-Credentials: true. Siempre refleja el origen del request si está en tu allowlist. - Valida orígenes explícitamente — mantén una allowlist de orígenes exactos. No hagas parseo o regex-match de orígenes sin validación cuidadosa para evitar bypasses.
- Setea
Vary: Origin— cuando los headers CORS varían por origen, añadeVary: Originpara que los caches no sirvan respuestas cross-origin a los dominios equivocados. - Mantén max-age de preflight razonable —
86400(1 día) es típico. Muy largo retrasa la propagación de cambios de política CORS; muy corto desperdicia requests de preflight. - Restringe métodos y headers permitidos — solo declara los métodos HTTP y headers que tu API realmente soporta. Un CORS sobre-permisivo expande la superficie de ataque.
Errores Comunes
- Setear
Access-Control-Allow-Origin: *y preguntarse por qué las cookies no funcionan cross-origin. - Reflejar el header
Origindel request sin validación, permitiendo que cualquier sitio web llame a tu API. - Olvidar manejar el preflight
OPTIONS, causando errores CORS del navegador en requests PUT/DELETE. - No setear
Vary: Origin, llevando a cache poisoning de CDN donde la respuesta de un origen se sirve a otro. - Habilitar
allowCredentialsen APIs públicas sin validación de origen, exponiendo endpoints autenticados a sitios maliciosos. Consulta Checklist de Seguridad de APIs para validación de origen.
Lectura Adicional
- Documentación oficial: consulta la referencia actualizada del framework o herramienta utilizada.
- Guías relacionadas: explora las guías de api y cors para profundizar.
- Patrones complementarios: revisa los patrones de diseño aplicables a tu stack tecnológico.
- Postmortems públicos: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica manejo correcto de cors cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Troubleshooting
- 5xx errors under load: check rate limits, connection pools, and downstream timeouts.
- CORS errors in the browser: confirm allowed origins, methods, and headers. Preflight requests must return the right headers before the actual request.
- Unexpected 404s: verify route definitions, path parameters, and base paths. Watch for trailing slashes and URL encoding differences.
- Authentication failures: validate token expiry, signature algorithms, and clock skew. Log rejected tokens without exposing secrets.
- Slow response times: profile the slowest percentiles.
Errores Comunes en Producción
- Copiar el ejemplo sin adaptarlo a volúmenes y modos de fallo reales.
- Saltar tests de carga e inyección de errores antes del primer despliegue productivo.
- Codificar valores fijos que deberían ser configurables por entorno.
- Olvidar agregar logging y monitoreo en cada paso.
- Desplegar sin plan de rollback ni estrategia de backup probada.
- Asumir que el ejemplo mínimo escalará sin agregar caché o procesamiento por lotes.
- No documentar la versión y configuración usadas en producción.
- Dejar la receta sin cambios cuando evolucionan las dependencias o la escala.
Preguntas frecuentes
¿Por qué mi API funciona en Postman pero falla en el navegador?
Postman no es un navegador — no aplica la Same-Origin Policy ni CORS. Los navegadores bloquean respuestas de requests cross-origin a menos que el servidor envíe los headers Access-Control-Allow-* apropiados. Testea la configuración CORS con DevTools reales del navegador o herramientas como curl con el header Origin.
¿Puedo usar un wildcard para subdominios como *.example.com?
No directamente en Access-Control-Allow-Origin. El header requiere una coincidencia exacta de origen. Puedes validar orígenes en vivo: verifica si el origen del request termina en .example.com en runtime y refleja el origen exacto de vuelta. Spring Boot allowedOriginPatterns soporta esto; en Express/Flask, implementa validación de origen custom.
¿Necesito CORS si despliego mi frontend y API en el mismo dominio?
No. CORS solo aplica cuando el origen (scheme + host + port) del frontend difiere del de la API. Si ambos corren en https://example.com (o la API está en un subdominio con configuración apropiada), no se necesitan headers CORS. Usar un reverse proxy (nginx) para rutear /api a tu backend es una estrategia común de deployment de mismo origen.
¿Cómo depuro errores CORS en el navegador?
Abre DevTools → pestaña Network. Busca requests fallidos con errores CORS en la consola. Chequea el header Access-Control-Allow-Origin en la respuesta — si falta o no coincide con el Origin del request, el navegador bloquea la respuesta. Para fallos de preflight, chequea el status y headers de la respuesta OPTIONS. Usa curl -H "Origin: https://yourapp.com" -X OPTIONS https://api.example.com/endpoint para testear preflight sin navegador. Causas comunes: servidor no enviando headers, origen no en allowlist, o Vary: Origin faltante causando cache issues de CDN.
¿Cómo manejo CORS con cookies y credenciales?
Setea Access-Control-Allow-Credentials: true en el servidor. El cliente debe enviar requests con credentials: 'include' (fetch) o withCredentials: true (axios). El header Access-Control-Allow-Origin debe ser un origen exacto — no * — cuando hay credenciales involucradas. El navegador rechaza Access-Control-Allow-Origin: * con Allow-Credentials: true. Asegúrate de que el atributo SameSite de la cookie esté seteado a None con Secure: true para cookies cross-site. Sin SameSite=None, los navegadores bloquean cookies en requests cross-origin.
¿Cómo configuro CORS en funciones serverless (AWS Lambda, Vercel)?
Retorna headers CORS en la respuesta de la función. Para API Gateway + Lambda, configura CORS en el method response del API Gateway o retorna headers desde la Lambda function. Para funciones de Vercel/Netlify, setea headers en el objeto de respuesta: res.setHeader('Access-Control-Allow-Origin', 'https://yourapp.com'). Maneja el preflight OPTIONS retornando un 204 con los headers apropiados inmediatamente. No dependas solo de la configuración CORS a nivel plataforma — verifica que los headers estén presentes en la respuesta real.
¿Cómo manejo CORS con WebSockets?
WebSockets no usan CORS — el navegador no enforcea la Same-Origin Policy para conexiones WebSocket. El servidor valida el header Origin durante el handshake del WebSocket. Rechaza conexiones con orígenes inesperados chequeando el header Origin en el upgrade request. No dependas de CORS para seguridad de WebSocket — implementa tokens de autenticación en la URL de conexión o subprotocol.
¿Cómo testeo configuración CORS?
Usa curl con el header Origin para verificar respuestas del servidor: curl -H "Origin: https://example.com" -I https://api.example.com/endpoint. Chequea que Access-Control-Allow-Origin coincida con el origen del request. Para preflight: curl -X OPTIONS -H "Origin: https://example.com" -H "Access-Control-Request-Method: PUT" -I https://api.example.com/endpoint. Usa DevTools del navegador para verificar que el navegador acepta la respuesta. Tests automatizados con Playwright pueden verificar comportamiento CORS end-to-end haciendo requests cross-origin desde un contexto real de navegador.
¿Cómo manejo CORS con service workers y Workbox?
Los service workers pueden interceptar y modificar respuestas, incluyendo agregar headers CORS. Sin embargo, no agregues headers CORS en un service worker — el navegador enforcea CORS antes de que el service worker vea la respuesta. Configura CORS en el servidor de origen. Para Workbox, setea cacheName y mode: 'cors' en las opciones de strategy. Si cacheas respuestas cross-origin, asegúrate de que el servidor envíe Access-Control-Allow-Origin y Vary: Origin. Las respuestas opaque (mode: 'no-cors') no pueden ser leídas por JavaScript — úsalas solo para cachear assets que no necesitan ser leídos.
¿Cómo manejo CORS para file uploads?
Los file uploads con multipart/form-data desde un origen diferente requieren CORS. El servidor debe permitir el método POST y Content-Type: multipart/form-data en Access-Control-Allow-Headers. Para uploads presigned S3, configura la política CORS del bucket S3: <CORSRule><AllowedOrigin>https://yourapp.com</AllowedOrigin><AllowedMethod>POST</AllowedMethod><AllowedHeader>*</AllowedHeader></CORSRule>. No uses Allow-Origin: * con uploads presigned si envías credenciales — especifica orígenes exactos.
Recursos Relacionados
Llamar a una API REST: Python, JS, Java y Go
Cómo hacer peticiones HTTP a una API REST y manejar la respuesta JSON en Python, JavaScript, Java y Go.
RecipeVersionado de APIs
Cómo versionar APIs REST y GraphQL para mantener compatibilidad hacia atrás mientras evolucionas tu interfaz.
RecipeManejar Errores en APIs con RFC 7807
Patrones para un manejo de errores de API consistente y predecible en varios lenguajes y frameworks.
RecipeLimitacion de tasa (Rate Limiting)
Cómo implementar rate limiting en APIs usando token bucket, sliding window y fixed window en Python, JavaScript y Java.
RecipeValidación de Input
Cómo validar input de usuarios de forma segura usando schemas, type checking y sanitización en Python, JavaScript y Java.
RecipeOpenAPI con Swagger UI y Redoc: guía práctica
Guía práctica para documentar APIs REST con OpenAPI. Genera docs interactivas con Swagger UI y Redoc en Python, JavaScript y Java con linting en CI.