Búsqueda de sitios web

javascript - encabezados HTTP en la API de cliente de Websockets


Parece que es fácil agregar encabezados HTTP personalizados a tu cliente websocket con cualquier cliente de encabezados HTTP que lo soporte, pero no puedo encontrar cómo hacerlo con la API WebSocket de la plataforma web.

¿Alguien tiene alguna idea de cómo lograrlo?

var ws = new WebSocket("ws://example.com/service");

Específicamente, necesito poder enviar un encabezado de autorización HTTP.

Respuesta #1

Actualizado 2 veces

Respuesta corta: No, solo se pueden especificar el campo de ruta y el de protocolo.

Respuesta más larga:

No existe un método en la API de WebSockets de JavaScript para especificar encabezados adicionales que el cliente/navegador deba enviar. La ruta HTTP ("GET /xyz") y el encabezado de protocolo ("Sec-WebSocket-Protocol") se pueden especificar en el constructor de WebSocket.

El encabezado Sec-WebSocket-Protocol (que a veces se amplía para usarse en la autenticación específica de websocket) se genera a partir del segundo argumento opcional del constructor de WebSocket:

var ws = new WebSocket("ws://example.com/path", "protocol");
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);

Los resultados anteriores producen los siguientes encabezados:

Sec-WebSocket-Protocol: protocol

y

Sec-WebSocket-Protocol: protocol1, protocol2

Un patrón común para lograr la autenticación/autorización de WebSocket es implementar un sistema de tickets donde la página que hospeda el cliente WebSocket solicita un ticket del servidor y luego pasa este ticket durante la configuración de la conexión WebSocket, ya sea en la URL/cadena de consulta, en el campo de protocolo, o requerido como el primer mensaje después de que se establece la conexión. El servidor entonces solo permite que la conexión continúe si el ticket es válido (existe, no ha sido usado previamente, la IP del cliente codificada en el ticket coincide, la marca de tiempo en el ticket es reciente, etc.). Aquí hay un resumen de información sobre seguridad de WebSocket: https://devcenter.heroku.com/articles/websocket-security

La autenticación básica era anteriormente una opción, pero esto ha quedado obsoleto y los navegadores modernos no envían el encabezado incluso si se especifica.

Información de autenticación básica (Obsoleta - Ya no funcional):

NOTA: la siguiente información ya no es precisa en ningún navegador moderno.

El encabezado de Autorización se genera a partir del nombre de usuario y la contraseña (o solo el nombre de usuario) del campo de la URI de WebSocket:

var ws = new WebSocket("ws://username:[email ")

Lo anterior da como resultado el siguiente encabezado con la cadena "username:password" codificada en base64:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

He probado la autenticación básica en Chrome 55 y Firefox 50 y he verificado que la información de autenticación básica se negocia efectivamente con el servidor (esto puede no funcionar en Safari).

Gracias a Dmitry Frank por la respuesta sobre la autenticación básica

Respuesta #2

Aquí hay un resumen rápido de la situación para el viajero cansado que tropieza con esto en 2025, y probablemente por mucho tiempo después de eso.

Nada ha cambiado en los 15(!) años desde que se abrió esta pregunta. La API de WebSocket de JavaScript ha sido abandonada por todos los proveedores de navegadores (aunque las implementaciones ocasionalmente reciben actualizaciones), y las nuevas especificaciones (WebSocket Stream y WebTransport) están lejos de materializarse. Todo esto significa que los WebSockets todavía se usan ampliamente, no existe un reemplazo para la API rota a pesar de que se le llamó "anticuada" hace 9 años, y los problemas descritos en la pregunta son tan molestos como siempre, si no más.

Las opciones para manejar la situación (spoiler, #5 o #6 es lo que quieres):

1. Implementar la autenticación externamente

Descrito en https://devcenter.heroku.com/articles/websocket-security. Se espera que el cliente haga una solicitud autenticada a un endpoint dedicado que generará y almacenará un token de corta duración que también será enviado al cliente. El cliente luego devuelve este token como un parámetro de URL al abrir un WebSocket. El servidor puede validarlo y aceptar/rechazar la actualización del protocolo. Esto requiere que el servidor implemente un mecanismo de autenticación completamente personalizado y con estado específicamente para WebSockets, lo cual es un paso demasiado ambicioso en muchos escenarios.

2. Enviar información de autenticación a través de WebSocket

Abres un WebSocket sin autenticarte, luego envías tu información de autenticación a través del WebSocket antes de hacer cualquier otra cosa. Esto en teoría suena lógico (y es aconsejado por los proveedores de navegadores), pero se desmorona con solo un pensamiento superficial. El servidor está diseñado para implementar un mecanismo de autenticación incómodo, altamente dependiente del estado y completamente personalizado que no funciona bien con nada más, además de tener que mantener una conexión persistente con un cliente que se niega a autenticarse, dejando la puerta abierta a ataques de denegación de servicio, o entrar en todo un nuevo agujero de conejo de hacer cumplir tiempos de espera rigurosos para prevenir comportamientos maliciosos.

3. Enviar información de autenticación (por ejemplo, un token de acceso) mediante un parámetro de URL

No es tan terrible como suena, siempre y cuando se haga cumplir SSL (wss:// y no ws://) porque las URL de WebSocket son especiales y no se guardan en el historial del navegador ni en algo similar. Además, los tokens de acceso normalmente tienen una vida corta, lo que también mitiga el peligro. Pero. Es muy probable que el servidor registre la URL en algún momento. Incluso si tu aplicación de servidor no lo hace, el framework o el host (en la nube) probablemente sí. Adicionalmente, si tienes que pasar tokens de ID (como suele hacer Firebase), podrías encontrarte con varias limitaciones de longitud de URL, ya que los tokens de ID pueden ser enormes.

4. Autenticación mediante una buena y vieja cookie

No lo hagas. Los WebSockets no están sujetos a la política del mismo origen (porque aparentemente cada pequeño detalle sobre los WebSockets tiene que ser horrible) y permitir cookies te dejaría completamente expuesto a ataques CSRF. Corregir esto usando tokens CSRF se describe, por ejemplo, aquí, pero es más difícil que tomar cualquier otro enfoque de esta lista, por lo que simplemente ni siquiera vale la pena considerarlo.

5. Contrabandear tokens de acceso dentro de Sec-WebSocket-Protocol

Dado que el único encabezado que un navegador permitirá que controles es Sec-WebSocket-Protocol, puedes abusar de él para emular cualquier otro encabezado. Curiosamente (o más bien cómicamente), esto es lo que Kubernetes está haciendo. En resumen, agregas lo que necesites para la autenticación como un subprotocolo adicional soportado dentro de Sec-WebSocket-Protocol:

var ws = new WebSocket("ws://example.com/path", ["realProtocol", "yourAccessTokenOrSimilar"]);

Luego, en el servidor, agregas algún tipo de middleware que transforme la solicitud de nuevo a su forma más sensata antes de pasarla más adelante en el sistema. Terrible, sí, pero hasta ahora la mejor solución. No hay tokens en la URL, no hay autenticación personalizada aparte del pequeño middleware, no se necesita estado adicional en el servidor. No olvides incluir el subprotocolo real, ya que varias herramientas rechazarán una conexión sin él.

6. Cambiar a SSE (o RSocket?), si corresponde

En un buen número de casos, SSE podría ser un reemplazo decente. La API de navegador EventSource está tan horriblemente rota como WebSocket (no puede enviar nada más que solicitudes GET, tampoco puede enviar encabezados a pesar de ser HTTP normal), ¡pero! puede ser reemplazada fácilmente por fetch, que es, por variar, una API más sensata. Este enfoque funciona bien como alternativa a WebSocket en, por ejemplo, suscripciones GraphQL, o realmente en cualquier lugar donde el dúplex completo no sea obligatorio, es decir, donde el cliente pueda enviar solicitudes únicas y no necesite transmitir activamente al servidor. Y eso probablemente cubre la mayoría de los escenarios. Teóricamente, RSocket también podría ser una opción, pero viendo cómo se implementa a través de WebSockets en el navegador, no creo que realmente resuelva nada, aunque no lo investigué lo suficiente como para decirlo con absoluta certeza.

Respuesta #3

Más bien una solución alternativa, pero todos los navegadores modernos envían las cookies del dominio junto con la conexión, así que usando:

var authToken = 'R3YKZFKBVi';

document.cookie = 'X-Authorization=' + authToken + '; path=/';

var ws = new WebSocket(
    'wss://localhost:9000/wss/'
);

Terminar con los encabezados de conexión de la solicitud:

Cookie: X-Authorization=R3YKZFKBVi

Respuesta #4

No es posible enviar el encabezado de autorización.

Adjuntar un parámetro de consulta de token es una opción. Sin embargo, en algunas circunstancias, puede no ser deseable enviar su token de inicio de sesión principal en texto claro como un parámetro de consulta porque es menos transparente que usar un encabezado y terminará siendo registrado en lugares desconocidos. Si esto genera preocupaciones de seguridad para usted, una alternativa es usar un token JWT secundario solo para las cosas del WebSocket.

Crea un endpoint REST para generar este JWT, el cual, por supuesto, solo puede ser accedido por usuarios autenticados con su token de inicio de sesión principal (transmitido vía encabezado). El JWT del socket web puede configurarse de manera diferente a su token de inicio de sesión, por ejemplo, con un tiempo de expiración más corto, por lo que es más seguro enviarlo como parámetro de consulta de su solicitud de actualización.

Crea un JwtAuthHandler separado para la misma ruta en la que registras el SockJS eventbusHandler. Asegúrate de que tu manejador de autenticación esté registrado primero, para que puedas verificar el token del WebSocket contra tu base de datos (el JWT debería estar de alguna manera vinculado a tu usuario en el backend).

Respuesta #5

El problema con el encabezado de autorización HTTP se puede abordar con lo siguiente:

var ws = new WebSocket("ws://username:[email /service");

Luego, se establecerá un encabezado HTTP de Autorización Básica adecuado con el nombre de usuario y la contraseña proporcionados. Si necesita Autorización Básica, entonces ya está todo listo.

Quiero usar Bearer, sin embargo, y recurrí al siguiente truco: me conecto al servidor de la siguiente manera:

var ws = new WebSocket("ws://[email /service");

Y cuando mi código en el lado del servidor recibe una cabecera de Autorización Básica con un nombre de usuario no vacío y una contraseña vacía, entonces interpreta el nombre de usuario como un token.

Respuesta #6

No se puede enviar un encabezado personalizado cuando se desea establecer una conexión WebSockets usando la API de WebSockets de JavaScript. Se pueden utilizar los encabezados Subprotocols utilizando el segundo constructor de la clase WebSocket:

var ws = new WebSocket("ws://example.com/service", "soap");

y luego puedes obtener los encabezados de Subprotocols usando la clave Sec-WebSocket-Protocol en el servidor.

También hay una limitación: los valores de los encabezados de tus subprotocolos no pueden contener una coma (,) !

Respuesta #7

No puedes agregar encabezados, pero, si solo necesitas pasar valores al servidor en el momento de la conexión, puedes especificar una parte de cadena de consulta en la URL:

var ws = new WebSocket("ws://example.com/service?key1=value1&key2=value2");

Esa URL es válida pero, por supuesto, necesitarás modificar el código de tu servidor para analizarla.

Respuesta #8

Para aquellos que aún tienen dificultades en 2021, la clase global de WebSockets de Node JS tiene un campo adicional options en el constructor. Si vas a la implementación de la clase WebSockets, encontrarás esta declaración de variable. Puedes ver que acepta tres parámetros: url, que es obligatorio, protocols (opcional), que puede ser una cadena, un arreglo de cadenas o nulo. Luego, un tercer parámetro que es options, de nuestro interés, un objeto y (todavía opcional). Ver ...

declare var WebSocket: {
    prototype: WebSocket;
    new (
        uri: string,
        protocols?: string | string[] | null,
        options?: {
            headers: { [headerName: string]: string };
            [optionName: string]: any;
        } | null,
    ): WebSocket;
    readonly CLOSED: number;
    readonly CLOSING: number;
    readonly CONNECTING: number;
    readonly OPEN: number;
};

Si estás usando una biblioteca de Node Js como react, react-native, aquí hay un ejemplo de cómo puedes hacerlo.

 const ws = new WebSocket(WEB_SOCKETS_URL, null, {
    headers: {
      ['Set-Cookie']: cookie,
    },
  });

Aviso de que los protocolos que he pasado son nulos. Si estás usando jwt, puedes pasar el encabezado Authorization con Bearer + token.

Aviso, esto podría no ser compatible con todos los navegadores de forma predeterminada, según los documentos web de MDN solo se documentan dos parámetros. vea https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#syntax

Artículos relacionados