tutorial room real rails cable active ruby-on-rails-5 actioncable

ruby on rails 5 - room - Enviar auth_token para la autenticación a ActionCable



ruby on rails real time updates (9)

module ApplicationCable class Connection < ActionCable::Connection::Base identified_by :current_user def connect #puts params[:auth_token] self.current_user = find_verified_user logger.add_tags ''ActionCable'', current_user.name end end end

No uso la web como punto final para el cable de acción, así que quiero usar auth_token para la autenticación. Por defecto, el cable de acción utiliza la identificación de usuario de sesión para la autenticación. ¿Cómo pasar params para conectar método?


Como ya dije en un comentario, la respuesta aceptada no es una buena idea , simplemente porque la convención es que la URL no debe contener datos tan confidenciales . Puede encontrar más información aquí: https://tools.ietf.org/html/rfc6750#section-5.3 (aunque esto es específicamente sobre OAuth).

Sin embargo, existe otro enfoque: usar la autenticación básica HTTP a través de la URL ws . Descubrí que la mayoría de los clientes de websocket le permiten establecer implícitamente los encabezados anteponiendo la url con una autenticación básica http como esta: wss://user:[email protected]/cable .

Esto agregará el encabezado de Authorization con un valor de Basic ... En mi caso, estaba usando el devise-jwt y simplemente implementé una estrategia heredada de la proporcionada en la gema que saca el jwt del encabezado de Authorization . Así que configuro el url de esta manera: wss://[email protected]/cable que establece el encabezado en este (pseudo): Basic base64("token:") y lo analizo en la estrategia.


Desafortunadamente para las conexiones de websocket, las cabeceras adicionales y las personalizadas no son compatibles con la mayoría de los 2 clientes y servidores de websocket. Así que las opciones posibles son:

  • Adjuntar como un parámetro de URL y analizarlo en el servidor

    path.to.api/cable?token=1234 # and parse it like request.params[:token]

Contras : podría ser vulnerable, ya que podría terminar en registros e información del proceso del sistema disponible para otros que tienen acceso al servidor, más here

Solución : cifre el token y adjúntelo, de modo que incluso si se puede ver en los registros, no serviría de nada hasta que se descifre.

  • Adjuntar JWT en uno de los parámetros permitidos.

Lado del cliente:

# Append jwt to protocols new WebSocket(url, existing_protocols.concat(jwt))

action-cable-react-jwt una action-cable-react-jwt biblioteca JS action-cable-react-jwt para React y React-Native que solo hace esto. Sientase libre de usarlo.

Lado del servidor:

# get the user by # self.current_user = find_verified_user def find_verified_user begin header_array = self.request.headers[:HTTP_SEC_WEBSOCKET_PROTOCOL].split('','') token = header_array[header_array.length-1] decoded_token = JWT.decode token, Rails.application.secrets.secret_key_base, true, { :algorithm => ''HS256'' } if (current_user = User.find((decoded_token[0])[''sub''])) current_user else reject_unauthorized_connection end rescue reject_unauthorized_connection end end

1 La mayoría de las API de Websocket (incluidas las de Mozilla''s ) son como la siguiente:

El constructor WebSocket acepta un parámetro requerido y un parámetro opcional:

WebSocket WebSocket( in DOMString url, in optional DOMString protocols ); WebSocket WebSocket( in DOMString url, in optional DOMString[] protocols );

url

La URL a la que conectarse; esta debe ser la URL a la que responderá el servidor WebSocket.

protocols opcionales

Ya sea una sola cadena de protocolo o una matriz de cadenas de protocolo. Estas cadenas se utilizan para indicar subprogramas, de modo que un solo servidor puede implementar múltiples subprivios de WebSocket (por ejemplo, es posible que desee que un servidor pueda manejar diferentes tipos de interacciones según el protocolo especificado). Si no especifica una cadena de protocolo, se asume una cadena vacía.

2 Siempre hay excepciones, por ejemplo, este node.js lib ws permite crear encabezados personalizados, por lo que puede usar el habitual Authorization: Bearer token encabezado de Authorization: Bearer token , y analizarlo en el servidor, pero tanto el cliente como el servidor deberían usar ws .


En caso de que alguno de ustedes quiera usar ActionCable.createCustomer. Pero tengo token renovable como lo hago yo:

const consumer = ActionCable.createConsumer("/cable") const consumer_url = consumer.url Object.defineProperty( consumer, ''url'', { get: function() { const token = localStorage.getItem(''auth-token'') const email = localStorage.getItem(''auth-email'') return consumer_url+"?email="+email+"&token="+token } }); return consumer;

Luego, en caso de que se pierda la conexión, se abrirá con un nuevo token nuevo.


En cuanto a la seguridad de la respuesta de Pierre : si está utilizando el protocolo WSS, que utiliza SSL para el cifrado, los principios para el envío de datos seguros deberían ser los mismos que para HTTPS. Cuando se utiliza SSL, los parámetros de la cadena de consulta se cifran, así como el cuerpo de la solicitud. Entonces, si en las API HTTP está enviando cualquier tipo de token a través de HTTPS y lo considera seguro, entonces debería ser el mismo para WSS. Solo recuerde que igual que para HTTPS, no envíe credenciales como contraseña a través de parámetros de consulta, ya que la URL de la solicitud podría registrarse en un servidor y, por lo tanto, almacenarse con su contraseña. En su lugar, utilice elementos como tokens que son emitidos por el servidor.

También puede consultar esto (esto básicamente describe algo como la autenticación JWT + la verificación de la dirección IP): https://devcenter.heroku.com/articles/websocket-security#authentication-authorization .


Logré enviar mi token de autenticación como un parámetro de consulta.

Al crear mi consumidor en mi aplicación javascript, paso el token en la URL del servidor de cable de esta manera:

wss://myapp.com/cable?token=1234

En mi conexión de cable, puedo obtener este token accediendo a request.params :

module ApplicationCable class Connection < ActionCable::Connection::Base identified_by :current_user def connect self.current_user = find_verified_user logger.add_tags ''ActionCable'', current_user.name end protected: def find_verified_user if current_user = User.find_by(token: request.params[:token]) current_user else reject_unauthorized_connection end end end end

Claramente no es ideal, pero no creo que puedas enviar encabezados personalizados al crear el websocket.


Otra forma (la forma en que lo hice al final en lugar de mi otra respuesta) sería tener una acción de authenticate en tu canal. Utilicé esto para determinar el usuario actual y configurarlo en la conexión / canal. Todas las cosas se envían a través de websockets, por lo que las credenciales no son un problema aquí cuando las tenemos encriptadas (es decir, wss ).


También es posible pasar el token de autenticación en los encabezados de solicitud y luego validar la conexión accediendo al hash de request.headers . Por ejemplo, si el token de autenticación se especificara en un encabezado llamado ''X-Auth-Token'' y su modelo de usuario tiene un campo auth_token que podría hacer:

module ApplicationCable class Connection < ActionCable::Connection::Base identified_by :current_user def connect self.current_user = find_verified_user logger.add_tags ''ActionCable'', current_user.id end protected def find_verified_user if current_user = User.find_by(auth_token: request.headers[''X-Auth-Token'']) current_user else reject_unauthorized_connection end end end end


para agregar a las respuestas anteriores, si usaste tu JWT como parámetro, deberás tener al menos btoa(your_token) @js y Base64.decode64(request.params[:token]) @rails como rails considera dot '' . un separador para que su ficha se corte @rails params side


La respuesta de Pierre funciona. Sin embargo, es una buena idea ser explícito acerca de esperar estos parámetros en su aplicación.

Por ejemplo, en uno de sus archivos de configuración (por ejemplo, application.rb , development.rb , etc ...) puede hacer esto:

config.action_cable.mount_path = ''/cable/:token''

Y luego simplemente acceda a él desde su clase de Connection con:

request.params[:token]