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?
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?
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