-
Notifications
You must be signed in to change notification settings - Fork 118
Description
I am trying to configure ActionCable in Rails 6.1.4.1 to run on a subdomain, but when I follow these instructions the env["warden"]
(which is used by Devise) is nil in app/channels/application_cable/connection.rb
.
This post will explain my environment/configuration for my attempts at getting things to work with a custom Rackup file, and then it will explain the changes I made to get things working. I've separated the sections with horizontal lines, for easier skimming.
This is a rather long post, so I want to thank you ahead of time for even reading this far.
- ActionCable is fully functional outside of any channels that expect authentication (so anything that is not using
env["warden"]
) - ActionCable is fully functional (including use of
env["warden"]
) if I do not use a subdomain (if I use alocation /cable
block within my main server block) - The site itself (main domain & subdomain) is fully functional - I can log into the site & visit either main or subdomain and be logged into both
I have the main domain (and www) proxied through CloudFlare, but I do not have the actioncable subdomain proxied.
My web infrastructure consists of a single EC2 instance.
I have ActionCable mounted in my routes file:
Rails.application.routes.draw do
mount ActionCable.server => "/cable"
end
I have the session_store set to share across subdomains, and the URL & allowed_request_origins set in my production.rb file:
config.session_store :cookie_store, key: "_app_session", same_site: :lax, domain: ".appdomain.com", tld_length: 2
config.action_cable.url = "wss://actioncable.appdomain.com/cable"
config.action_cable.allowed_request_origins = ["https://appdomain.com", "https://actioncable.appdomain.com", /https:\/\/*.appdomain.com/]
I have a cable/config.ru
file specifically for the ActionCable server:
require_relative "../config/environment"
Rails.application.eager_load!
run ActionCable.server
My Nginx config forces SSL & non-www, and runs ActionCable separately:
# Force SSL
server {
listen 80;
listen [::]:80;
return 301 https://appdomain.com$request_uri;
}
# Force non-www in SSL
server {
listen [::]:443 ssl http2;
listen 443 ssl http2;
server_name www.appdomain.com;
gzip off;
ssl_certificate /etc/letsencrypt/live/appdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/appdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
ssl_ecdh_curve secp384r1;
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS; # Score=90 (recommended)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 10s;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
return 301 https://appdomain.com$request_uri;
}
# The actual site
server {
listen [::]:443 ssl http2;
listen 443 ssl http2;
server_name appdomain.com;
gzip off;
ssl_certificate /etc/letsencrypt/live/appdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/appdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
ssl_ecdh_curve secp384r1;
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS; # Score=90 (recommended)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 10s;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
error_page 500 502 503 504 /500.html;
keepalive_timeout 10;
passenger_enabled on;
passenger_env_var LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2;
passenger_ruby /home/myself/.rubies/ruby-3.0.2/bin/ruby;
passenger_app_env production;
passenger_min_instances 4;
root /home/myself/production/current/public;
# ActionCable; allowing this because I'm moving from subfolder to subdomain & have active clients
location /cable {
# return 302 https://actioncable.appdomain.com/cable; # Once this is working, I plan on forcing clients over to the subdomain
passenger_app_group_name app_action_cable_f;
passenger_force_max_concurrent_requests_per_process 0;
}
}
# ActionCable
server {
listen 443;
server_name actioncable.appdomain.com;
root /home/myself/production/current/public;
passenger_enabled on;
passenger_env_var LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2;
passenger_ruby /home/myself/.rubies/ruby-3.0.2/bin/ruby;
passenger_app_env production;
passenger_app_type rack;
passenger_min_instances 4;
passenger_startup_file cable/config.ru;
location /cable {
passenger_app_group_name app_action_cable_sd;
passenger_force_max_concurrent_requests_per_process 0;
}
}
Things do work properly if I comment out these custom Rackup file lines in the ActionCable server in nginx:
passenger_app_type rack;
passenger_startup_file cable/config.ru;
This effectively means that I'm running two instances of my Rails app, so if I want to keep traffic out of the subdomain, I can also add a redirect before the location /cable
line, resulting in this server block:
server {
listen 443;
server_name actioncable.appdomain.com;
root /home/myself/production/current/public;
passenger_enabled on;
passenger_env_var LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2;
passenger_ruby /home/myself/.rubies/ruby-3.0.2/bin/ruby;
passenger_app_env production;
passenger_min_instances 4;
location / {
return 301 https://appdomain.com$request_uri;
}
location /cable {
passenger_app_group_name app_action_cable_sd;
passenger_force_max_concurrent_requests_per_process 0;
}
}
An aside: I don't understand why none of the ssl-related lines are required in my ActionCable server instance. 🤷 Everything works without specifying ssl_certificate
etc within that ActionCable server block.