A common way to handle authentication/authorization of websocket connections is to use cookie sent on initial http upgrade request. This is shown here http://edgeguides.rubyonrails.org/action_cable_overview.html
But what if I don't want to use cookie, but rather a token? Couple of hacks discussed here Send auth_token for authentication to ActionCable but it's too hacky imo.
What if I move the token authorization logic to channel:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :connection_id
def connect
self.connection_id = SecureRandom.hex # accept anyone, identify by random string
end
end
end
This will accept any connection. And than:
class TestChannel < ApplicationCable::Channel
def subscribed
if authorize(params[:token]) # check the token here
stream_from "test:1"
else
connection.close # alternatively `reject_subscription` to keep ws connection open
end
end
def unsubscribed
stop_all_streams
end
end
Frontend subscription will look like this
App.test = App.cable.subscriptions.create(channel: "TestChannel", token: "hello-world")
This seems to work. The individual connection is identified by a random string and each channel subscription holds this unique connection. When broadcast to this channel is made it will push the data to all subscribers through their unique connections. So if we rejected a subscriber here it won't receive anything. But we cannot close unauthorized connections before subscription attempt. I can just open the connection to /cable and hold it as long as I want.
But is this safe? The obvious problem here is that anybody can open as many ws connection as he like. Is this the only problem with such approach?