Authentication and Devise with broadcasts

What is the equivalent of the Connection class in Rails for Turbo::Stream ?
I want to use an identified_by method with Devise for the streams, but I end up with an error like this:
[Turbo::Streams::ActionBroadcastJob] [1fdec594-eeb2-4a37-94f5-3d7e172ec03f] Error performing Turbo::Streams::ActionBroadcastJob (Job ID: 1fdec594-eeb2-4a37-94f5-3d7e172ec03f) from Async(default) in 6.58ms: ActionView::Template::Error (Devise could not find theWarden::Proxyinstance on your request environment. Make sure that your application is loading Devise and Warden as expected and that theWarden::Manager middleware is present in your middleware stack.

1 Like

Thank you for sending me piece of the source-code. I’m not sure how this solve the issue of broadcasting a partial that contains a current_user set by devise. I guess that it means we could send a partial to a specific user and then again, as long as the partial doesn’t reference a current_user

Partials used for turbo streaming have to be free of global references, as they’re rendered by the ApplicationRenderer, not within the context of a specific request.

9 Likes

So it means there is no way we can access user details (from current_user) in our streams? Is there any workaround?

Use case:

Chat application with messages where sender is specified.

This is how I made it work, essentially by breaking up broadcasts call on the model into individual broadcasts per ActiveRecord action (create, update, destroy) and passing in user as a parameter into the partial: Making Hotwire and Devise play nicely

I’ve tried follow your post.

Making Hotwire and Devise play nicely.

But I still get below error.

NameError - undefined local variable or method `current_user' for #<Chat:0x00007feac3ac0890>:
  app/models/chat.rb:9:in `block in <class:Chat>'
  app/controllers/a/chats_controller.rb:14:in `block in create'
  app/controllers/a/chats_controller.rb:13:in `create'

Do you have a public repository of the post.
I want to see your whole implements.

Unfortunately I don’t have a full repository at the moment but I’m working on it. In the meantime, I’d be happy to look at your code snippet to see if I can find anything.

1 Like

I had a situation whereby (taking the tutorial example) when the current user created a new message I wanted the appended message to render for them with edit / delete actions but not for other users.

The solution I went with was to use a turbo stream to target one div on the current users rendered page and broadcasts to target a different one. e.g.

<% if current_user %>
  <%= turbo_frame_tag "new_message", src: new_room_message_path(@room), target: "_top" %>
  <div id="current_user_messages">
    <%= render @room.messages %>
  </div>
<% else %>
  <%= turbo_stream_from "messages" %>
  <div id="messages">
    <%= render @room.messages %>
  </div>
<% end %>

# create.trubo_stream
<%= turbo_stream.prepend "current_user_messages" do %>
  # so you can use current user logically when rendering template
  <%= render partial: "message", locals: { message: @message current_user: current_user } %>
<% end %>

# message.rb
after_create_commit { broadcast_prepend_to 'messages' }

# messages_controller.rb
...
respond_to do |format|
  format.turbo_stream
end

# _message.html.erb
<%= turbo_frame_tag dom_id(message do %>
   ....
  <% if current_user %>
    <div><%= link_to 'Edit'...></div>%>
  <% end %>
<% end %>

Hi @ksajadi,
I tried your solutions but I am getting undefined local variable user.

You can ignore this. I was able to implement the functionality with Hotwire like @dhh advised.

Do I understand correctly @dhh response says it’s not possible to use turbo streams for a private user page listing all of his private content, and get real-time updates when his private content changes?

Sorry DHH and all involved in this discussion but I’m trying to use devise with hotwire but it’s seems to have problems, in my Message model:

after_create_commit do
    broadcast_append_to "messages_#{self.room_id}", target: "messages_#{self.room_id}", locals: { current_user: self.user }
    broadcast_update_to "messages", target: "count_message_#{self.room_id}", html: self.room.messages.count, locals: { current_user: self.user }
  end

I need to pass current_user to my partial. In the view I just have

<h5>Messages</h5>
<%= turbo_stream_from "messages_#{@room.id}" %>
<%= turbo_frame_tag "messages_#{@room.id}" do %>
  <%= render @room.messages, locals: {current_user: current_user} %>
<% end %>

But this error is showing up:

ActionView::Template::Error (Devise could not find the `Warden::Proxy` instance on your request environment.
Make sure that your application is loading Devise and Warden as expected and that the `Warden::Manager` middleware is present in your middleware stack.

I know it is pretty confusing, but you have to understand that when broadcasting, you have to get rid of current_user. I had hard time too with that. Here is another way of thinking about it.
Imagine a new message broadcasted to the author and 6 other users. When Rails render the _message partial, it knows who is the author (current_user), but don’t know about the other 6 users. It will broadcast to the author and the 6 other users the same rendered partial, with the edit link showed.

Best way to avoid that is to have partial not relying on current_user at all. If you absolutely need to render the message with stuffs linked to each user seeing it, you can send tell the browser to load the new message via a fetch request

1 Like

just landed on this convo; felt bad.

Our app heavily relies on pundit (most of our activerecord requests goes through PunditScope, which takes current_user as the first argument).

Using turborails is then a no go for us, if I understand @Teddy_Valente point ?

@benbonnet We switched from using broadcasting through the model to broadcasting changes through the controller.

For example, we moved broadcasting from Todo to the todos controller:

    def create
      @todo = @checklist.todos.new(todo_params)

      respond_to do |format|
        if @todo.save
          @todo.broadcast_append_later_to @todo.checklist
          format.turbo_stream
        else
          format.html { head :unprocessable_entity }
        end
      end
    end

In other places in our app, that’s allowed us to use current_user as a local.

This almost worked for me, but the issue I ran into if another user (or myself on another device) created a message, it’d be broadcast to “messages” stream instead of the current_user one. So I would not receive the new broadcasted message.

I ended up including the message.user_id as a data attribute in the partial. I then used CSS to hide it if it didn’t match the current user based on this How to pass `current_user.id` to a controller? - #4 by javan

Another solution I considered was storing the current user in the DOM, and then in the head tag have <meta name="current-current-id" content="<%= current_user.id %>">. I could then use a stimulus controller to retrieve current_user.id and hide certain elements(delete/edit buttons) if they didn’t match the message.user_id. The simpler CSS one solved my use case for now so I stuck with that.

2 Likes

I ended up including the message.user_id as a data attribute in the partial. I then used CSS to hide it if it didn’t match the current user

This way is a huge security hole. You can’t trust client-side post-processing of the data you’re receiving from the backend. It is just super easy to use Chrome-inspector with the Network tab open and get all the data, which should not be shown to the user.

Any updates to broadcast from the model with a current user in the view ?