¿Cómo habilitar CORS con HTTPOnly Cookie para asegurar el token?
Publicado: 2021-12-29En este artículo, vemos cómo habilitar CORS (uso compartido de recursos de origen cruzado) con la cookie HTTPOnly para asegurar nuestros tokens de acceso.
Hoy en día, los servidores backend y los clientes frontend se implementan en diferentes dominios. Por lo tanto, el servidor debe 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 una sesión. Por seguridad, es mejor almacenar tokens en cookies HTTPOnly.
¿Por qué se bloquean las solicitudes de origen cruzado?
Supongamos que nuestra aplicación frontend se implementó en https://app.geekflare.com . Un script cargado en https://app.geekflare.com solo puede solicitar recursos del mismo origen.
Siempre que intentamos enviar una solicitud de origen cruzado a otro dominio https://api.geekflare.com u otro puerto https://app.geekflare.com:3000 u otro esquema http://app.geekflare.com , el La solicitud de origen cruzado será bloqueada por el navegador.
Pero, ¿por qué la misma solicitud bloqueada por el navegador se envía desde cualquier servidor backend mediante solicitud curl o se envía mediante herramientas como el cartero sin ningún problema de CORS? En realidad, es por seguridad proteger a los usuarios de ataques como CSRF (Falsificación de solicitudes entre sitios).
Tomemos un ejemplo, supongamos que cualquier usuario inicia sesión en su propia cuenta de PayPal en su navegador. Si podemos enviar una solicitud de origen cruzado a paypal.com desde un script cargado en otro dominio, malicious.com sin ningún error / bloqueo de CORS, como enviamos la solicitud del mismo origen.
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 una URL corta para ocultar la URL real. Cuando el usuario hace clic en un enlace malicioso, el script cargado en el dominio malicious.com enviará una solicitud de origen cruzado a PayPal para transferir el monto del usuario a la cuenta de PayPal del atacante que se ejecutará. Todos los usuarios que hayan iniciado sesión en su cuenta PayPal y hayan hecho clic en este enlace malicioso perderán su dinero. Cualquiera puede robar dinero fácilmente sin el conocimiento de un usuario de una cuenta de PayPal.
Por el motivo anterior, los navegadores bloquean todas las solicitudes de origen cruzado.
¿Qué es CORS (intercambio de recursos de origen cruzado)?
CORS es un mecanismo de seguridad basado en encabezados que utiliza el servidor para indicarle al navegador que envíe una solicitud de origen cruzado desde dominios de confianza.
El servidor habilitado con encabezados CORS se usa para evitar solicitudes de origen cruzado bloqueadas por navegadores.
¿Cómo funciona CORS?
Como el servidor ya definió su dominio de confianza en su configuración CORS. Cuando enviamos una solicitud al servidor, la respuesta le dirá al navegador que el dominio solicitado es confiable o no en su encabezado.
Hay dos tipos de solicitudes CORS:
- Solicitud simple
- Solicitud de verificación previa
Solicitud simple:

- 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.
- Después de recibir la solicitud, el navegador verificará que el valor del encabezado de origen enviado ( https://app.geekflare.com ) y el valor de origen de control de acceso recibido ( https://app.geekflare.com ) sean iguales o comodín(*). De lo contrario, arrojará un error CORS.
Solicitud de verificación previa:

- Dependiendo del parámetro de solicitud personalizada de la solicitud de origen cruzado, como métodos (PUT, DELETE) o encabezados personalizados o diferentes tipos de contenido, etc. El navegador decidirá enviar una solicitud de OPCIONES de verificación previa para verificar si la solicitud real es segura para enviar O no.
- Después de recibir la respuesta (código de estado: 204, que significa que no hay contenido), el navegador verificará los parámetros de permiso de control de acceso para la solicitud real. Si el servidor permite los parámetros de la solicitud. La solicitud real de origen cruzado 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 los encabezados CORS para permitir el origen, métodos, encabezados personalizados, credenciales, etc.
El navegador lee el encabezado CORS del servidor y permite las solicitudes reales del cliente solo después de verificar los parámetros de la solicitud.
- Access-Control-Allow-Origin: para especificar dominios exactos (https://app.geekflate.com, https://lab.geekflare.com) o comodín (*)
- Access-Control-Allow-Methods: Permitir los métodos HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) que solo nosotros necesitamos.
- Access-Control-Allow-Headers: para permitir solo encabezados específicos (autorización, csrf-token)
- Access-Control-Allow-Credentials: valor booleano utilizado para permitir credenciales de origen cruzado (cookies, encabezado de autorización).
- Access-Control-Max-Age: le dice al navegador que guarde en caché la respuesta de verificación previa durante algún tiempo.
- Access-Control-Expose-Headers: especifique los encabezados a los que puede acceder el script del lado del cliente.
Para habilitar CORS en el servidor web apache y Nginx, siga este tutorial.
Habilitación de 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('CORS-enabled web server listening on port 80') })En el ejemplo anterior, hemos habilitado el punto final de la API de los usuarios para los métodos POST, PUT, GET pero no el método DELETE.
Para habilitar CORS fácilmente en la aplicación ExpressJS, puede instalar cors
npm install corsAcceso-Control-Permitir-Origen
Habilitando CORS para todos los dominios
app.use(cors({ origin: '*' }));Habilitación de CORS para un solo dominio
app.use(cors({ origin: 'https://app.geekflare.com' }));Si desea permitir CORS para el origen https://app.geekflare.com y https://lab.geekflare.com
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ] }));Métodos de permiso de control de acceso
Para habilitar CORS para todos los métodos, omita esta opción en el módulo CORS en ExpressJS. Pero para habilitar métodos específicos (GET, POST, PUT).
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'] }));Access-Control-Allow-Headers
Se utiliza para permitir que los encabezados distintos de los predeterminados se envíen con 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'] }));Acceso-Control-Permitir-Credenciales
Omita esto si no quiere decirle al navegador que permita las credenciales a pedido, incluso si con Credenciales está configurado en verdadero .
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
Para que el navegador guarde en caché la información de respuesta previa al vuelo en la caché durante un segundo específico. 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 ExposHeaders, no expondrá el encabezado de Autorización. Entonces 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 también expondrá todos los encabezados y el encabezado de autorización.
¿Qué es una cookie HTTP?
Una cookie es un pequeño dato que el servidor enviará al navegador del cliente. En solicitudes posteriores, el navegador enviará todas las cookies relacionadas con el mismo dominio en cada solicitud.
Cookie tiene su atributo, que se puede definir para hacer que una cookie funcione de manera diferente a como lo necesitemos.
- Nombre Nombre de la cookie.
- valor: datos de la cookie con respecto al nombre de la cookie
- Dominio: las cookies se enviarán solo al dominio definido
- Ruta: cookies enviadas solo después de la ruta de prefijo de URL definida. Supongamos que hemos definido nuestra ruta de cookies como path = 'admin /'. Cookies no enviadas para la URL https://geekflare.com/expire/ pero enviadas con el prefijo de URL https://geekflare.com/admin/
- Max-Age / Expires (número en segundos): ¿ Cuándo debe caducar la cookie? La vida útil de la cookie invalida la cookie después del tiempo especificado.
- HTTPOnly (booleano): el servidor backend puede acceder a esa cookie HTTPOnly pero no al script del lado del cliente cuando es verdadero.
- Seguro (booleano): las cookies solo se envían a través de un dominio SSL / TLS cuando son verdaderas.
- sameSite (string [Strict, Lax, None]): se utiliza para habilitar / restringir las cookies enviadas en solicitudes entre sitios. Para conocer más detalles sobre las cookies
sameSiteconsulte MDN. Acepta tres opciones Strict, Lax, None. El valor seguro de la cookie se establece en verdadero para la configuración de la cookie sameSite = None.
¿Por qué HTTPOnly cookie para tokens?
El almacenamiento del token de acceso enviado desde el servidor en el almacenamiento del lado del cliente, como el almacenamiento local , la base de datos indexada y las cookies (HTTPOnly no establecido en verdadero) es más vulnerable al ataque XSS. Supongamos que alguna de sus páginas es débil ante un ataque XSS. Los atacantes pueden hacer un mal uso de los tokens de usuario almacenados en el navegador.
Las cookies HTTPOnly solo se configuran / obtienen mediante el servidor / backend, pero no en el lado del cliente.
Script del lado del cliente restringido para acceder a esa cookie solo HTTP. Por lo tanto, las cookies HTTPOnly no son vulnerables a los ataques XSS y son más seguras. Porque solo es accesible por el servidor.
Habilitar la cookie HTTPOnly en el backend habilitado para CORS
Habilitar Cookie en CORS necesita la siguiente configuración en la aplicación / servidor.
- Establezca el encabezado Access-Control-Allow-Credentials en verdadero.
- Access-Control-Allow-Origin y Access-Control-Allow-Headers no deben ser comodines (*).
- El atributo de cookie sameSite debe ser Ninguno.
- Para habilitar el valor sameSite en ninguno, establezca el valor seguro en verdadero: habilite el backend con certificado SSL / TLS para que funcione en el nombre de dominio.
Veamos un código de ejemplo que establece un token de acceso en la cookie HTTPOnly después de verificar 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)), //second min hour days year secure: true, // set to true if your using https or samesite is none httpOnly: true, // backend only sameSite: 'none' // set to none for 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 las cookies CORS y HTTPOnly implementando los cuatro pasos anteriores en su idioma de backend y servidor web.
Puede seguir este tutorial para apache y Nginx para habilitar CORS siguiendo los pasos anteriores.
withCredentials for Cross-Origin request
Credenciales (Cookie, Autorización) enviadas con la solicitud del mismo origen de forma predeterminada. Para el origen cruzado, tenemos que especificar withCredentials como verdadero.
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 = trueConclusión
Espero que el artículo anterior lo ayude a comprender cómo funciona CORS y habilitar CORS para solicitudes de origen cruzado en el servidor. Por qué almacenar cookies en HTTPOnly es seguro y cómo se utilizan con credenciales en clientes para solicitudes de origen cruzado.
