Turbo Stream Security

I’m a bit surprised not to have found standard documentation on this. If I have missed it, please point me in the right direction.

I’m just starting with turbo streams. I have followed the goRails tutorial here:

This lets everyone subscribe and get tweet updates.

My question is what happens when you’re dealing with a resource that is owned by a user.

How do you ensure that updates only go to users who have authorisation for a given resource.

Previously when I used websockets, there was a whole authentication dance. Is that handled for me with the signed stream identifiers?

It looks like

#https://github.com/hotwired/turbo-rails/blob/main/app/helpers/turbo/streams_helper.rb
turbo_stream_from

is set up to handle security by wrapping identifiers into the stream name, (and the framework I’m using - jumpstart pro - seems to be doing that in practice) - but I can’t see any documentation on how this is supposed to be configured/used - and can’t even figure out where/how it is happening…

thanks in advance…

I can’t see it being any different to html. To access a stream, you still have to hit a route, and a controller action. You can handle authorisation at that level. And then stream responses accordingly.

I may just be fundamentally misunderstanding things here. I’m thinking about broadcasts from model updates.

	after_create_commit {broadcast_prepend_to "apps"}
	after_update_commit {broadcast_replace_to "apps"}
	after_destroy_commit {broadcast_remove_to "apps"}

that’s the code in the GoRails demo. Taking that unchanged;

If user1 creates an item, then that item is broadcast to user2, so user2 sees the resource which they’re not supposed to.

Clearly that is the intent in the demo - but it isn’t suitable in my case.

I tried broadcasting to apps_#{account_id} and using

    = turbo_stream_from "apps_#{current_account.id}"
    = turbo_frame_tag "apps_#{current_account.id}" do

but
a) that doesn’t work (I’m not sure why)
b) I don’t understand the setup sufficiently to know whether that is safe/sufficient

that sounds like logic needs to exist to whether to broadcast at all (ie. don’t broadcast to unauthorised users), or perhaps, whether to broadcast something different depending on whether users are authorised or not? in your case it sounds like the former.

I’m quite certain it is a known and common problem which people would have had to solve. but i’ve never had to to it personally (yet)…someone on this forum might be better able to help you?

You can pass an active model to turbo_stream_from - rails will generate a unique id for the individual record (ultimately it calls to_gid_param)

= turbo_stream_from @customer 

Just using the id doesn’t stop someone from manually subscribing to any id they see fit.

You can authorise connections to a stream as per - ActionCable - Part 3 - Securing Your WebSockets | Drifting Ruby

Action cable is still there

I think this recent commit brings back the ability to specify specific channels also - looks like presently they are all handled by “Turbo::StreamsChannel”

looks like the documentation is here in the code:

I guess it is early days for this tech - but it does seem like this should be covered in the handbook…

  # Used in the view to create a subscription to a stream identified by the <tt>streamables</tt> running over the
  # <tt>Turbo::StreamsChannel</tt>. The stream name being generated is safe to embed in the HTML sent to a user without
  # fear of tampering, as it is signed using <tt>Turbo.signed_stream_verifier</tt>. Example:
  #
  #   # app/views/entries/index.html.erb
  #   <%= turbo_stream_from Current.account, :entries %>
  #   <div id="entries">New entries will be appended to this target</div>
  #
  # The example above will process all turbo streams sent to a stream name like <tt>account:5:entries</tt>
  # (when Current.account.id = 5). Updates to this stream can be sent like
  # <tt>entry.broadcast_append_to entry.account, :entries, target: "entries"</tt>.

This is because the broadcastable module and stream_helpers are not a main part of turbo rather turbo is an independent javascript framework designed to be integrated within many different programming languages. This code right here is from the turbo-rails gem and It is the Rails integration with turbo they made to show how it could be integrated with a backend. That is why it is not mentioned in the handbook because it acts separately from turbo

It is mentioned right here in the handbook that it is a reference implementation not a main part of hotwire Turbo Handbook

Fair enough - I’ll plead guilty to ‘rails first’ thinking!

Having said that, I imagine a lot of users are coming from Rails - and I’m still surprised that there isn’t more documentation on this (even if within turbo-rails rather than the handbook)

Perhaps I’ll post an article :slight_smile:

3 Likes

Yea I know what you mean I was pretty confused about this part as well that could be really helpful!