Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebSocket connection failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header

I am setting up a chat feature using ActionCable in a Rails API / React project, and trying to figure out the best solution to authenticate connections.

The project uses the devise-jwt gem, so my Authorization is passed as a header. Since Web Sockets does not allow headers, I've been searching for the best alternative. Solutions I have found suggest one of the following (with various caveats):

  • sending the JWT as a query parameter in the url
  • hijacking the HTTP_SEC_WEBSOCKET_PROTOCOL & sending it that way

Neither of these solutions feel particular nice, but I've tried to go with the second because sending sensitive information in the url (even an encoded JWT) just feels wrong.

Here is the code in my connection.rb:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    private

    def find_verified_user
      begin
        jwt = request.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
        decoded_jwt = JWT.decode(jwt, Rails.application.credentials.devise_jwt_secret_key)[0]
        id = decoded_jwt['sub']

        case decoded_jwt['scp']
        when 'admin'
          Admin.where(id: id).first
        when 'hero'
          Hero.where(id: id).first
        when'villain'
          Villain.where(id: id).first
        else
          reject_unauthorized_connection
        end
      rescue
        reject_unauthorized_connection
      end
    end
  end
end

I have three different User models: Admin, (and let's call them) Hero & Villain, because reasons.

Now, my actual probelm: In the server I can get the JWT, decode it, and find my Admin, but in the chrome console when I try to create the socket I get: WebSocket connection to 'ws://localhost:3000/cable' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received

That is in response to running the following code in the chrome console:

token = "jwt.etc.blah..."
socket = new WebSocket(url, [token]);

The server itself seems alright with things:

Started GET "/cable" for ::1 at 2020-07-22 18:26:02 +0100
Started GET "/cable/" [WebSocket] for ::1 at 2020-07-22 18:26:02 +0100
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
Finished "/cable/" [WebSocket] for ::1 at 2020-07-22 18:26:02 +0100
  Admin Load (0.2ms)  SELECT "admins".* FROM "admins" WHERE "admins"."id" = $1 ORDER BY "admins"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/channels/application_cable/connection.rb:19:in `find_verified_user'
Registered connection (Z2lkOi8vYXBpNTUtbWFpcy9BZG1pbi8x)

The error seems to be telling that I need some kind of confirmation or information about the extra protocol that I've hackily snuck my JWT into... but I've no idea how to go about doing this.

Any help would be greatly appreciated! Is it even possible to send additional protocols with rails ActionCable? Or am I going to have to resort to using a query param? Or is there an even neater way that I can tackle this problem?

First time I've tried anything with web sockets, so thanks in advance for help!

like image 864
siansian Avatar asked May 02 '26 21:05

siansian


1 Answers

Passing credentials via Sec-WebSocket-Protocol is a common workaround for WebSocket authentication for web based applications since browsers do not support sending custom headers while connection initiation.

If you are passing a value via Sec-WebSocket-Protocol header, your server has to return the same header/value pair back to the client.

Since Sec-WebSocket-Protocol header is not intended for passing credentials to the WebSocket server, you have to play by its rules and treat it like a protocol and fulfill its requirements.

like image 149
Sarpdoruk Tahmaz Avatar answered May 04 '26 13:05

Sarpdoruk Tahmaz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!