Geekflare recibe el apoyo de nuestra audiencia. Podemos ganar comisiones de afiliación de los enlaces de compra en este sitio.
En Desarrollo y Seguridad Última actualización: 25 de septiembre de 2023
Compartir en:
Escáner de seguridad de aplicaciones web Invicti - la única solución que ofrece verificación automática de vulnerabilidades con Proof-Based Scanning™.

En este artículo, veremos cómo habilitar CORS (Cross-Origin Resource Sharing) con la cookie HTTPOnly para asegurar nuestros tokens de acceso

Hoy en día, los servidores backend y los clientes frontend se despliegan en dominios diferentes. Por lo tanto, el servidor tiene que habilitar CORS para permitir que los clientes se comuniquen con el servidor en los navegadores

Además, los servidores están implementando la autenticación sin estado para una mejor escalabilidad. Los tokens se almacenan y mantienen en el lado del cliente, pero no en el lado del servidor como en la sesión. Por seguridad, es mejor almacenar los tokens en cookies HTTPOnly

¿Por qué se bloquean las solicitudes Cross-Origin?

Supongamos que nuestra aplicación frontend se despliega en https://app.geekflare.com. Un script cargado en https://app.geekflare.comcansólo solicita recursos del mismo origen

Siempre que intentemos enviar una solicitud de origen cruzado a otro dominio https://api.geekflare.com o a otro puerto https://app.geekflare.com:3000 o a otro esquema http://app.geekflare.com, la solicitud de origen cruzado será bloqueada por el navegador

Pero por qué la misma petición bloqueada por el navegador puede ser enviada desde cualquier servidor backend usando curl request o enviada usando herramientas como el postman sin ningún problema CORS. En realidad es por seguridad para proteger a los usuarios de ataques como CSRF(Cross-Site Request Forgery)

Pongamos un ejemplo, supongamos que cualquier usuario inicia sesión en su propia cuenta PayPal en su navegador. Si podemos enviar una solicitud cross-origin a paypal. com desde un script cargado en otro dominio malicious. com sin ningún error CORS/bloqueo como si enviáramos la solicitud same-origin

Los atacantes pueden enviar fácilmente su página maliciosa https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account convirtiéndola en short-URL para ocultar la URL real. Cuando el usuario haga clic en el enlace malicioso, el script cargado en el dominio malicious. com enviará una solicitud de origen cruzado a PayPal para transferir el importe del usuario a la cuenta PayPal del atacante se ejecutará. Todos los usuarios que hayan iniciado sesión en su cuenta PayPal y hayan pulsado este enlace malicioso perderán su dinero. Cualquiera puede robar dinero fácilmente sin que el usuario de la cuenta PayPal lo sepa

Por esta razón, los navegadores bloquean todas las peticiones de origen cruzado

¿Qué es CORS(Cross-Origin Resource Sharing)?

CORS es un mecanismo de seguridad basado en cabeceras que utiliza el servidor para indicar al navegador que envíe una solicitud de origen cruzado desde dominios de confianza
El servidor habilitado con cabeceras CORS se utiliza para evitar que las peticiones de origen cruzado sean bloqueadas por los navegadores

¿Cómo funciona CORS?

El servidor ya ha definido su dominio de confianza en su configuración CORS. Cuando enviamos una petición al servidor, la respuesta indicará al navegador el dominio solicitado es de confianza o no en su cabecera

Existen dos tipos de peticiones CORS

  • Petición simple
  • Solicitud previa

Solicitud simple:

CORS-simple flujo de solicitud dice que envía una solicitud de origen cruzado, pero cuando recibió la respuesta. Comprueba las cabeceras.

  • El navegador envía la solicitud a un dominio de origen cruzado con origen(https://app.geekflare.com).
  • El servidor devuelve la respuesta correspondiente con los métodos permitidos y el origen permitido.
  • Tras recibir la solicitud, el navegador comprobará que el valor de la cabecera de origen enviada(https://app.geekflare.com) y el valor de access-control-allow-origin recibido(https://app.geekflare.com) son iguales o comodín(*). En caso contrario, arrojará un error CORS.

Solicitud de verificación previa:

CORS-Preflight Request Imagen que muestra el flujo de la petición cross-origin con la petición OPTIONS preflight antes de enviar la petición real para verificar las cabeceras.

  • Dependiendo del parámetro de solicitud personalizado de la solicitud de origen cruzado como métodos(PUT, DELETE) o cabeceras personalizadas o diferente tipo de contenido, etc. El navegador decidirá enviar una solicitud OPTIONS de verificación previa para comprobar si la solicitud real es segura de enviar o no.
  • Tras recibir la respuesta (código de estado: 204, que significa sin contenido), el navegador comprobará los parámetros de control de acceso permitidos para la solicitud real. Si los parámetros de la petición están permitidos por el servidor. La solicitud cross-origin real enviada y recibida

Si access-control-allow-origin:*,entonces la respuesta está permitida para todos los orígenes. Pero no es seguro a menos que lo necesite

¿Cómo habilitar CORS?

Para habilitar CORS para cualquier dominio, habilite las cabeceras CORS para permitir origen, métodos, cabeceras personalizadas, credenciales, etc.

El navegador lee la cabecera CORS del servidor y permite las peticiones reales del cliente sólo después de verificar los parámetros de la petición

  • Access-Control-Allow-Origin: Para especificar dominios exactos(https://app.geekflate.com, https://lab.geekflare.com) o comodines(*)
  • Access-Control-Allow-Methods: Para permitir los métodos HTTP(GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) que sólo nosotros necesitamos.
  • Access-Control-Allow-Headers: Para permitir sólo Cabeceras específicas(Autorización, csrf-token)
  • Control de acceso - Permitir credenciales: Valor booleano utilizado para permitir cross-origin-credentials(cookies, authorization header).
  • Access-Control-Max-Age : Indica al navegador que almacene en caché la respuesta de verificación previa durante cierto tiempo.
  • Access-Control-Expose-Headers: Especifica las cabeceras que son accesibles por el script del lado del cliente.

Para habilitar CORS en el servidor web apache y Nginx, siga este tutorial

Habilitar CORS en ExpressJS

Tomemos una aplicación ExpressJS de ejemplo sin CORS

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

app.get('/users', function (req, res, next) {
 res.json({msg: 'user get'})
});

app.post('/users', function (req, res, next) {
 res.json({msg: 'user create'})
});

app.put('/users', function (req, res, next) {
 res.json({msg: 'User update'})
});

app.listen(80, function () {
 console.log('Servidor web habilitado para CORS escuchando en el puerto 80')

}

)

En el ejemplo anterior, hemos habilitado el punto final de la API de usuarios para los métodos POST, PUT, GET pero no para el método DELETE

Para habilitar CORS fácilmente en la aplicación ExpressJS, puede instalar el cors

npm install cor

s

Access-Control-Allow-Origin

Habilitación de CORS para todo el dominio

app.use(cors({
 origin: '*'

}

))

Habilitación de CORS para un único dominio
app.use(cors({


 origen: 'https://app.geekflare.com'
}))

Si desea permitir CORS para el origen https://app.geekflare.com y https://lab.geekflare.com
aplicación

.use(cors({
 origen: [
 'https://app.geekflare.com',
 'https://lab.geekflare.com'
 ]
}))

Control-de-acceso-Permitir-métodos

Para habilitar CORS para todos los métodos, omita esta opción en el módulo CORS en el ExpressJS. Pero para habilitar métodos específicos(GET, POST, PUT)

app.use(cors({
 origen: [
 'https://app.geekflare.com',
 'https://lab.geekflare.com'
 ],
 métodos: ['GET', 'PUT', 'POST']

}

))

Access-Control-Allow-Headers

Se utiliza para permitir que se envíen cabeceras distintas de las predeterminadas con las solicitudes reales

app.use(cors({
 origin: [
 'https://app.geekflare.com',
 'https://lab.geekflare.com'
 ],
 methods: ['GET', 'PUT', 'POST'],
 allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token']
})

Access-Control-Allow-Credentials

Omita esto si no quiere decirle al navegador que permita las credenciales en la solicitud incluso cuando withCredentials esté establecido en true

app.use(cors({
 origin: [
 'https://app.geekflare.com',
 'https://lab.geekflare.com'
 ],
 methods: ['GET', 'PUT', 'POST'],
 allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
 credentials: true

}

)

Access-Control-Max-Age

Indicar al navegador que almacene en caché la información de respuesta previa a la comprobación durante un segundo especificado. Omita esto si no desea almacenar en caché la respuesta

app.use(cors({
 origin: [
 'https://app.geekflare.com',
 'https://lab.geekflare.com'
 ],
 methods: ['GET', 'PUT', 'POST'],
 allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
 credentials: true,
 maxAge: 600

}

)

La respuesta de verificación previa almacenada en caché estará disponible durante 10 minutos en el navegador

Access-Control-Expose-Headers

app.use(cors({
 origin: [
 'https://app.geekflare.com',
 'https://lab.geekflare.com'
 ],
 methods: ['GET', 'PUT', 'POST'],
 allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
 credentials: true,
 maxAge: 600,
 exposedHeaders: ['Content-Range', 'X-Content-Range']

}

))

Si ponemos el comodín(*) en exposedHeaders , no expondrá la cabecera Autorización. Así que tenemos que exponer explícitamente como a continuación

app.use(cors({
 origin: [
 'https://app.geekflare.com',
 'https://lab.geekflare.com'
 ],
 methods: ['GET', 'PUT', 'POST'],
 allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
 credentials: true,
 maxAge: 600,
 exposedHeaders: ['*', 'Authorization', ]

}

))

Lo anterior expondrá todas las cabeceras y también la de Autorización

¿Qué es una cookie HTTP?

Una cookie es un pequeño fragmento de datos que el servidor enviará al navegador del cliente. En peticiones posteriores, el navegador enviará todas las cookies relacionadas con el mismo dominio en cada petición.

La cookie tiene sus atributos, que pueden definirse para hacer que una cookie funcione de forma diferente según nuestras necesidades

  • Nombre Nombre de la cookie.
  • valor: datos de la cookie correspondientes al nombre de la cookie
  • Dominio: las cookies se enviarán sólo al dominio definido
  • Ruta : las cookies se enviarán sólo a la ruta con prefijo URL definida. Supongamos que hemos definido la ruta de nuestra cookie como path='admin/'. Las cookies no se enviarán para la URL https://geekflare.com/expire/ sino que se enviarán con el prefijo de URL https://geekflare.com/admin/
  • Max-Age/Expires(número en segundos): Cuándo debe caducar la cookie. El tiempo de vida de la cookie hace que la cookie no sea válida después del tiempo especificado.
  • HTTPOnly(booleano): El servidor backend puede acceder a esa cookie HTTPOnly pero no el script del lado del cliente cuando es true.
  • Seguro(Booleano): Las cookies sólo se envían a través de un dominio SSL/TLS cuando es verdadero.
  • sameSite(cadena [Estricta, Laxa, Ninguna]): Se utiliza para habilitar/restringir las cookies enviadas a través de peticiones cross-site. Para conocer más detalles sobre las cookies sameSite consulte MDN. Acepta tres opciones Strict, Lax, None. Valor seguro de la cookie establecido en true para la configuración de la cookie sameSite=None.

¿Por qué cookie HTTPOnly para tokens?

Almacenar el token de acceso enviado desde el servidor en el almacenamiento del lado del cliente como almacenamiento local, DB indexada y galleta (HTTPOnly no configurado a true) son más vulnerables a ataques XSS. Suponga que alguna de sus páginas es débil a un ataque XSS. Los atacantes podrían utilizar indebidamente los tokens de usuario almacenados en el navegador

Las cookies HTTPOnly sólo son establecidas/obtenidas por el servidor/backend pero no en el lado del cliente

El script del lado del cliente tiene restringido el acceso a esa cookie HTTPonly. Así que las cookies HTTPOnly no son vulnerables a los ataques XSS y son más seguras. Porque sólo es accesible por el servidor

Habilitar cookie HTTPOnly en CORS habilitado backend

Habilitar la cookie en CORS necesita la siguiente configuración en la aplicación/servidor

  • Establezca el encabezado Access-Control-Allow-Credentials en true.
  • Access-Control-Allow-Origin y Access-Control-Allow-Headers no deben ser comodines(*).
  • El atributo cookie sameSite debe ser None.
  • Para habilitar el valor sameSite a none, establezca el valor secure a true: Habilitar backend con certificado SSL/TLS para trabajar en el nombre de dominio.

Veamos un código de ejemplo que establece un token de acceso en la cookie HTTPOnly tras comprobar las credenciales de inicio de sesión

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

app.use(cors({
 origin: [
 'https://app.geekflare.com',
 'https://lab.geekflare.com'
 ],
 methods: ['GET', 'PUT', 'POST'],
 allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'],
 credentials: true,
 maxAge: 600,
 exposedHeaders: ['*', 'Authorization' ]
}));

app.post('/login', function (req, res, next) {
 res.cookie('access_token', access_token, {
 expires: new Date(Date.now() (3600 * 1000 * 24 * 180 * 1)), /segundo min hora días año
 secure: true, // establecer en true si está utilizando https o samesite es none
 httpOnly: true, // sólo backend
 sameSite: 'none' // establecer a none para cross-request
 });

 res.json({ msg: 'Login Successfully', access_token });
});

app.listen(80, function () {
 console.log('CORS-enabled web server listening on port 80')

}

) 

Puede configurar CORS y cookies HTTPOnly implementando los cuatro pasos anteriores en su lenguaje backend y servidor web

Puede seguir este tutorial para apache y Nginx para habilitar CORS siguiendo los pasos anteriores

withCredentials para peticiones Cross-Origin

Credenciales(Cookie, Autorización) enviadas con la petición del mismo-origen por defecto. Para cross-origin, tenemos que especificar el withCredentials a true

API XMLHttpRequest

var xhr = new XMLHttpRequest();

xhr

.open('GET', 'http://api.geekflare.com/user', true);

xhr

.withCredentials = true;

xhr

.send(null)

Obtener API

fetch('http://api.geekflare.com/user', {
 credentials: 'include'

}

)

JQuery Ajax

$.ajax({
 url: 'http://api.geekflare.com/user',
 xhrFields: {
 withCredentials: true
 }

}

)

Axios

axios

.defaults.withCredentials =

verdadero

Conclusión

Espero que el artículo anterior le ayude a entender cómo funciona CORS y a habilitar CORS para peticiones de origen cruzado en el servidor. Por qué almacenar cookies en HTTPOnly es seguro y cómo se utiliza withCredentials en clientes para peticiones de origen cruzado.

  • Aghilan Baskar
    Autor
Gracias a nuestros patrocinadores
Más lecturas sobre desarrollo
Potencia tu negocio
Algunas de las herramientas y servicios que le ayudarán a hacer crecer su negocio.
  • Invicti utiliza el Proof-Based Scanning™ para verificar automáticamente las vulnerabilidades identificadas y generar resultados procesables en tan solo unas horas.
    Pruebe Invicti
  • Web scraping, proxy residencial, gestor de proxy, desbloqueador web, rastreador de motores de búsqueda, y todo lo que necesita para recopilar datos web.
    Pruebe Brightdata
  • Monday.com es un sistema operativo de trabajo todo en uno que te ayuda a gestionar proyectos, tareas, trabajo, ventas, CRM, operaciones, flujos de trabajo y mucho más.
    Prueba Monday
  • Intruder es un escáner de vulnerabilidades en línea que encuentra puntos débiles de ciberseguridad en su infraestructura, para evitar costosas violaciones de datos.
    Prueba Intruder