CORS: que es, por que existe y como configurarlo
El problema: Same-Origin Policy
Los navegadores implementan una politica de seguridad llamada Same-Origin Policy. Esta regla dice que una pagina web solo puede hacer requests al mismo dominio desde el que fue cargada.
Dos URLs tienen el mismo origen si comparten el mismo protocolo, dominio y puerto.
| URL de la pagina | URL del request | Mismo origen? |
|---|---|---|
https://miapp.com |
https://miapp.com/api/datos |
Si |
https://miapp.com |
https://api.miapp.com/datos |
No (distinto subdominio) |
https://miapp.com |
http://miapp.com/api/datos |
No (distinto protocolo) |
https://miapp.com |
https://otra-app.com/api |
No (distinto dominio) |
Si el origen no coincide, el navegador bloquea el request. Esto protege a los usuarios, pero tambien genera un problema: muchas aplicaciones modernas necesitan que el frontend y la API esten en dominios distintos.
La solucion: CORS
CORS (Cross-Origin Resource Sharing) es un mecanismo que permite a una API indicarle al navegador: "esta bien, acepto requests desde este otro dominio".
Funciona a traves de headers HTTP que la API incluye en sus respuestas.
El mas importante es Access-Control-Allow-Origin.
miapp.com
api.miapp.com
Allow-Origin header
permite la respuesta
Si la API no incluye el header o el origen no coincide, el navegador descarta la respuesta y muestra un error de CORS en la consola.
sitio-malicioso.com
api.miapp.com
Allow-Origin
bloquea la respuesta
El preflight request
Antes de enviar ciertos requests (POST con JSON, requests con headers custom, etc.), el navegador envia automaticamente un request OPTIONS llamado preflight. Es como preguntar "puedo enviar este request?".
La API responde con los headers CORS indicando que origenes, metodos y headers acepta. Si todo coincide, el navegador procede con el request real.
# El navegador envia automaticamente:
OPTIONS /api/datos HTTP/1.1
Origin: https://miapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
# La API responde:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://miapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
Los headers CORS principales
| Header | Que hace |
|---|---|
Access-Control-Allow-Origin |
Indica que origenes pueden acceder a la API |
Access-Control-Allow-Methods |
Que metodos HTTP estan permitidos (GET, POST, etc.) |
Access-Control-Allow-Headers |
Que headers custom puede enviar el cliente |
Access-Control-Allow-Credentials |
Si se permiten cookies/tokens en el request |
Access-Control-Max-Age |
Cuanto tiempo (en segundos) cachear el resultado del preflight |
Ejemplo practico: configurar CORS en FastAPI
FastAPI usa el CORSMiddleware de Starlette para manejar CORS. La configuracion basica se ve asi:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://miapp.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
Con esta configuracion, solo https://miapp.com puede hacer requests a la API.
Cualquier otro origen es rechazado.
El error comun: allow_origins=["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Acepta CUALQUIER origen
allow_credentials=True, # Con cookies/tokens incluidos
allow_methods=["*"],
allow_headers=["*"],
)
Usar ["*"] como origenes permitidos le dice al navegador que cualquier sitio web del mundo
puede hacer requests a tu API. Combinado con allow_credentials=True, un sitio malicioso podria
ejecutar JavaScript que haga requests usando la sesion activa de un usuario autenticado.
Esto abre la puerta a:
- CSRF (Cross-Site Request Forgery): un atacante ejecuta acciones en nombre del usuario
- Robo de datos: un script malicioso lee respuestas de tu API con las credenciales del usuario
- Explotacion remota: cualquier vulnerabilidad de tu API puede ser explotada desde cualquier sitio
La forma correcta: origenes por entorno
La buena practica es definir exactamente que dominios estan permitidos en cada entorno.
En desarrollo necesitas localhost, en produccion solo tu dominio real.
ALLOWED_ORIGINS_BY_ENVIRONMENT = {
"local": [
"http://localhost:4200",
"http://127.0.0.1:4200",
],
"dev": [
"https://dev.miapp.com",
],
"production": [
"https://miapp.com",
],
}
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS_BY_ENVIRONMENT.get(ENVIRONMENT, []),
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
Si el entorno no esta en el diccionario, .get(ENVIRONMENT, []) devuelve una lista vacia,
lo que rechaza todos los origenes. Seguro por defecto.
Como verificar que funciona
Podes testear CORS con un simple curl simulando un preflight request:
Origen permitido
curl -i -X OPTIONS https://miapp.com/api/health \
-H "Origin: https://miapp.com" \
-H "Access-Control-Request-Method: GET"
access-control-allow-origin: https://miapp.com
Origen no permitido
curl -i -X OPTIONS https://miapp.com/api/health \
-H "Origin: https://sitio-malicioso.com" \
-H "Access-Control-Request-Method: GET"
access-control-allow-origin, o devuelve un error 400.
Resumen
| Concepto | Descripcion |
|---|---|
| Same-Origin Policy | El navegador bloquea requests a dominios distintos por defecto |
| CORS | Mecanismo que permite a la API declarar que origenes acepta |
| Preflight (OPTIONS) | Request automatico del navegador para verificar permisos antes del request real |
allow_origins=["*"] |
Acepta cualquier origen. No usar en produccion |
| Origenes por entorno | Cada entorno solo acepta su propio dominio. Seguro por defecto |
Conclusion
CORS no es algo que "hay que resolver para que funcione el frontend". Es una capa de seguridad del navegador
que protege a tus usuarios. Configurar ["*"] es desactivar esa proteccion.
La configuracion correcta es explicita: cada entorno declara exactamente que dominios puede aceptar. Es un cambio simple que cierra una superficie de ataque importante.