No cookies are generally supported in Rails 5 API. See: http://guides.rubyonrails.org/api_app.html#creating-a-new-application .
If you do a common HTTP-authentification at first somewhere at your site ( with devise_token_auth gem), then you get 3 auth headers - access_token
, client
, uid
.
In such case you can use the Basic authentification for your Websockets connection (according https://devcenter.heroku.com/articles/websocket-security#authentication-authorization ) using these 3 auth headers:
Call (I use Chrome Simple WebSocket Client):
ws://localhost:3000/cable/?access-token=ZigtvvcKK7B7rsF_20bGHg&client=TccPxBrirWOO9k5fK4l_NA&[email protected]
Then process:
# Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading.
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
params = request.query_parameters()
access_token = params["access-token"]
uid = params["uid"]
client = params["client"]
self.current_user = find_verified_user access_token, uid, client
logger.add_tags 'ActionCable', current_user.email
end
protected
def find_verified_user token, uid, client_id # this checks whether a user is authenticated with devise
user = User.find_by email: uid
# http://www.rubydoc.info/gems/devise_token_auth/0.1.38/DeviseTokenAuth%2FConcerns%2FUser:valid_token%3F
if user && user.valid_token?(token, client_id)
user
else
reject_unauthorized_connection
end
end
end
end
This is similar to the common Websockets auth https://rubytutorial.io/actioncable-devise-authentication/
Such authentication is probably enough. I believe it is not necessary additionally to auth at the channel subscription and on every Websocket message sent to server:
http://guides.rubyonrails.org/action_cable_overview.html#server-side-components-connections
For every WebSocket accepted by the server, a connection object is instantiated. This object becomes the parent of all the channel subscriptions that are created from there on. The connection itself does not deal with any specific application logic beyond authentication and authorization.
So if your connection is identified_by :current_user
, you can later access current_user
wherever inside your FooChannel < ApplicationCable::Channel
! Example:
class AppearanceChannel < ApplicationCable::Channel
def subscribed
stream_from "appearance_channel"
if current_user
ActionCable.server.broadcast "appearance_channel", { user: current_user.id, online: :on }
current_user.online = true
current_user.save!
end
end
def unsubscribed
if current_user
# Any cleanup needed when channel is unsubscribed
ActionCable.server.broadcast "appearance_channel", { user: current_user.id, online: :off }
current_user.online = false
current_user.save!
end
end
end
PS I you want to use cookies in Rails 5 API, you can switch it on:
http://guides.rubyonrails.org/api_app.html#other-middleware
config/application.rb
config.middleware.use ActionDispatch::Cookies
http://guides.rubyonrails.org/api_app.html#adding-other-modules
controllers/api/application_controller.rb
class Api::ApplicationController < ActionController::API
include ActionController::Cookies
...
App.cable = ActionCable.createConsumer('/cable/' + token);
I can't figure how to get the token from the user. – Macro