Search code examples
ruby-on-railsnginxruby-on-rails-5actioncable

Debug Action Cable with Puma and Nginx configuration


Action Cable works in development but doesn't work in production. I have no idea how to debug it or how to narrow down the problem. In production I use puma (3.12.0), Nginx (1.10.3), Redis (3.2.6) and Rails 5.2.2 (Ruby 2.5.3p105) on a Debian 9 system. Everything but Action Cable works fine.

The rails production log ends with these lines:

I, [...]  INFO -- : [ae5f4486-eadd-466d-a4de-fd8db3cdcfb4] Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: upgrade, HTTP_UPGRADE: websocket)
D, [...] DEBUG -- :   ESC[1mESC[36mUser Load (0.8ms)ESC[0m  ESC[1mESC[34mSELECT  "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT $1ESC[0m  [["LIMIT", 1]]
E, [...] ERROR -- : An unauthorized connection attempt was rejected
E, [...] ERROR -- : Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: upgrade, HTTP_UPGRADE: websocket)
I, [...]  INFO -- : Finished "/cable/" [WebSocket] for 127.0.0.1 at 2019-01-14 15:52:53 +0100
I, [...]  INFO -- : Finished "/cable/" [WebSocket] for 127.0.0.1 at 2019-01-14 15:52:53 +0100
I, [...]  INFO -- : [58aa5ff7-e440-4050-88b6-6e9dcdc691a0] Started GET "/cable" for 127.0.0.1 at 2019-01-14 15:55:35 +0100
I, [...]  INFO -- : [58aa5ff7-e440-4050-88b6-6e9dcdc691a0] Started GET "/cable/" [WebSocket] for 127.0.0.1 at 2019-01-14 15:55:35 +0100
I, [...]  INFO -- : [58aa5ff7-e440-4050-88b6-6e9dcdc691a0] Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: upgrade, HTTP_UPGRADE: websocket)
D, [...] DEBUG -- :   ESC[1mESC[36mUser Load (0.8ms)ESC[0m  ESC[1mESC[34mSELECT  "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT $1ESC[0m  [["LIMIT", 1]]
E, [...] ERROR -- : An unauthorized connection attempt was rejected
E, [...] ERROR -- : Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: upgrade, HTTP_UPGRADE: websocket)
I, [...]  INFO -- : Finished "/cable/" [WebSocket] for 127.0.0.1 at 2019-01-14 15:55:35 +0100
I, [...]  INFO -- : Finished "/cable/" [WebSocket] for 127.0.0.1 at 2019-01-14 15:55:35 +0100

Nginx configuration:

upstream my_app {
  server unix:/tmp/example.sock;
}

server {
  listen 443 ssl;
  # ... ssl configuration

  server_name xyz.example.com;

  root /var/www/example/current/public;

  location /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  location / {
    try_files $uri @ruby;
  }

  location @ruby {
    proxy_pass http://my_app;

    proxy_set_header  Host $host;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $scheme;
    proxy_set_header  X-Forwarded-Ssl on; # Optional
    proxy_set_header  X-Forwarded-Port $server_port;
    proxy_set_header  X-Forwarded-Host $host;

    proxy_redirect off;
  }

  location /cable {
    proxy_pass http://my_app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

In the production.rb I have these lines:

config.action_cable.url = 'wss://xyz.example.com/cable'
config.action_cable.disable_request_forgery_protection = true

app/channels/application_cable/connection.rb

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

    def connect
      # This is a websocket so we have no warden and no session here
      # How to reuse the login made with devise?
      # http://www.rubytutorial.io/actioncable-devise-authentication/
      self.current_user = find_verified_user
    end

    private

    def find_verified_user
      if verified_user = User.find_by(id: cookies.signed["user.id"])
        verified_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

I don't see the An unauthorized connection attempt was rejected error in my development log so I guess that this must be the problem. But googling that phrase doesn't help.

How can I narrow down the problem?


Solution

  • It looks to me like the ActionCable part is working, but there is an authentication error. Successfully upgraded to WebSocket means it opened a websocket connection with the user, but An unauthorized connection attempt was rejected is saying that you are rejecting their connection somewhere in your code.

    Check your connection.rb file, are you calling reject_unauthorized_connection somewhere?

    A possible Fix

    app/channels/application_cable/connection.rb

    module ApplicationCable
      class Connection < ActionCable::Connection::Base
        identified_by :current_user
    
        def connect
          self.current_user = find_verified_user
        end
    
        protected
    
          def find_verified_user
            if current_user = env['warden'].user
              current_user
            else
              reject_unauthorized_connection
            end
          end
      end
    end