En 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 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 tokens en cookies HTTPOnly.

Why are Cross-Origin requests blocked?

Supongamos que nuestra aplicación frontend implementada en https://app.geekflare.com. Un guión cargado en https://app.geekflare.comsolo puede solicitar recursos del mismo origen.

Siempre que intentemos 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 navegador bloqueará la solicitud de origen cruzado.

Pero, ¿por qué la misma solicitud bloqueada por el navegador se envía desde cualquier servidor back-end usando la solicitud curl o se envía usando herramientas como el cartero sin ningún problema de CORS? En realidad, es por seguridad para proteger a los usuarios de ataques como CSRF (falsificación de solicitud entre sitios).

Tomemos un ejemplo, supongamos que cualquier usuario inició 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.

Atacantes puede enviar fácilmente su página maliciosa https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account  convirtiéndolo en una URL corta para ocultar la URL real. Cuando el usuario hace clic en un enlace malicioso, la secuencia de comandos cargada 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.

What is CORS(Cross-Origin Resource Sharing)?

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 flujo de solicitud CORS-simple dice que envía una solicitud de origen cruzado pero cuando recibió una respuesta. Comprueba los encabezados.

 

  • 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 métodos permitidos origen permitido.
  • Después de recibir la solicitud, el navegador verificará el valor del encabezado de origen enviado (https://app.geekflare.com) y recibió el valor de origen de permiso de control de acceso (https://app.geekflare.com) son iguales o comodines (*). De lo contrario, arrojará un error CORS.

Solicitud de verificación previa:

Imagen de solicitud de verificación previa de CORS que muestra el flujo de la solicitud de origen cruzado con la solicitud de verificación previa OPCIONES antes de enviar la solicitud real para verificar los encabezados.

  • 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, lo que significa que no hay contenido), el navegador buscará el control-de-acceso-permitido parámetros para la solicitud real. Si el servidor permite los parámetros de la solicitud. La solicitud real de origen cruzado enviada y recibida

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

How to enable 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.

  • Acceso-Control-Permitir-Origen: Para especificar dominios exactos (https://app.geekflate.com, https://lab.geekflare.com) o comodín (*)
  • Métodos de permiso de control de acceso: Permitir los métodos HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) que solo nosotros necesitamos.
  • Acceso-Control-Permitir-Encabezados: Para permitir solo encabezados específicos (autorización, csrf-token)
  • Acceso-Control-Permitir-Credenciales: Valor booleano utilizado para permitir credenciales de origen cruzado (cookies, encabezado de autorización).
  • Access-Control-Max-Edad: Le dice al navegador que guarde en caché la respuesta previa al vuelo durante algún tiempo.
  • Acceso-Control-Exposición-Encabezados: Especifique los encabezados a los que se puede acceder mediante un script del lado del cliente.

Para habilitar CORS en el servidor web apache y Nginx, siga esto tutoriales.

Habilitación de CORS en ExpressJS

Tomemos un ejemplo de aplicación ExpressJS 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 el cuernos

npm install cors

Acceso-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']
}));

Acceso-Control-Permitir-Encabezados

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 credenciales a pedido incluso en conCredentials está establecido 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-Edad

Para intimar al navegador a almacenar en caché la información de respuesta de verificación previa en el 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.

Acceso-Control-Exponer-Encabezados

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 expuestosEncabezados, no expondrá el encabezado de 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 también expondrá todos los encabezados y el encabezado de autorización.

What is an HTTP cookie?

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 de acceso: cookies enviadas solo después de la ruta del prefijo URL definido. Supongamos que hemos definido nuestra ruta de cookies como path='admin/'. Las cookies no se envían para la URL https://geekflare.com/expire/ pero se envían con el prefijo de URL https://geekflare.com/admin/
  • Max-Age / Caduca (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 a la secuencia de comandos del lado del cliente cuando es verdadera.
  • 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 sameSite ver DND. 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.

Why HTTPOnly cookie for tokens?

Almacenar el token de acceso enviado desde el servidor en el almacenamiento del lado del cliente como almacenamiento local, base de datos indexada, y galleta (HTTPOnly no establecido en true) son más vulnerables a 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 HTTPonly. 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.

Enable HTTPOnly cookie in CORS enabled backend

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.

Puedes seguir esto tutoriales 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);

Fetch API

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

JQueryAjax

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

Axios

axios.defaults.withCredentials = true

Conclusió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.