How to pass `current_user.id` to a controller?

I currently pass the current user id via data, but this feels hackish and not very secure.
Is there a better way?

Doesn’t seem hackish to me. Data-attributes are a great way for the server to pass necessary information to the client side code.

As far as security goes, everything you do on the client side is inherently insecure. Security should always be enforced server-side. If you don’t want to reveal user ids to the client use a unique random token instead.

2 Likes

In my code, a user locks a record in the UI, and this triggers the generation of a new html snippet that will be propagated to all viewers of this record to show this record is now locked.
The snippet contains 2 parts, one for the locker (an AJAX link to unlock the record), and one for everybody else (just a image).
Those 2 parts are hidden by default, and it’s the Stimulus controller role to reveal one and remove the other based on the current user identity based on

  may_unlock = (current_user_id == object.locker.id

The *Channel.rb knows about current_user; if only there was a simple (meaning: “synchronous”) way for the client javascript code to obtain this value.

CSS might be a better tool for the job if you just need to show/hide elements. Something like:

<head>
  …
  <style>
    [data-visible-to]:not([data-visible-to="<%= current_user.id %>"]) {
      display: none;
    }
  </style>
</head>
<body>
  …
  <div data-visible-to="<%= record.creator.id %>">
    …
  </div>
</body>
4 Likes

Thanks @javan Simple and elegant solution; it helped me remove a lot of painful javascript. (after adding the matching [data-invisible-to..] - see below)

It’s limited to simple cases though: I’m afraid it won’t help for

   may_edit = current_user_is_admin || (current_user_id == article_author_id) 

Here is the complete css code I used to replace showing/hiding with Javascript:

<head>
  ...
  <style>
    [data-visible-to]:not([data-visible-to="<%= current_user.id %>"]) {
      display: none;
    }
    [data-invisible-to="<%= current_user.id %>"] {
      display: none;
    }
  </style>
</head>

I expanded the above solution to hide/show elements based on the user’s roles.

<head>
  ..
  <%= csrf_meta_tag %>
  <%= render 'layouts/custom_user_styles' %>
</head>

file ‘views/layouts/_custom_user_styles.html.erb’:

<% cache current_user do %>
  <!--source: https://discuss.hotwired.dev/t/how-to-pass-current-user-id-to-a-controller/287/3-->
  <style>
    [data-visible-to-user]:not([data-visible-to-user="<%= current_user.id %>"]) {
      display: none;
    }
    [data-invisible-to-user="<%= current_user.id %>"] {
      display: none;
    }

    <% User::ROLES_SYMBOLS.each do |role| %>
      <% if current_user.has_role?(role) %>
        [data-invisible-to-role="<%= role %>"] {
          display: none;
        }
      <% else %>
        [data-visible-to-role="<%= role %>"] {
          display: none;
        }
      <% end %>
    <% end %>
  </style>
<% end %>

2 Likes

I think you could improve this further by caching based on the set of roles instead of each individual user

In case if you need to pass user id to more than one controller I would suggest go the Basecamp way and add it to the <head> like:

<meta name="current-person-id" content="your-id">
3 Likes

I agree with @Alexandr_K. This way you can keep your views free of user specific attributes, which allows you to cache the page. The meta(s) tag can be read to gather additional information about the current user.

If the user does need to be secure, you can do an ajax call to the server when the Stimulus component connects.

You saved me! Thank you so much!!!

In my case I have multiple devise models. A combo of your solution and this SO answer led me to this solution:
application_controller.rb

class ApplicationController < ActionController::Base
  devise_group :viewer, contains: [:admin, :source, :user]
end

<% cache current_viewer do %>
  <style>
    <% ['Admin', 'User', 'Source', 'NilClass'].each do |role| %>
      <% if current_viewer&.class&.name == role %>
        .data-invisible-to-<%= role.downcase %> {
          display: none !important;
        }
      <% else %>
        .data-visible-to-<%= role.downcase %> {
          display: none !important;
        }
      <% end %>
    <% end %>
  </style>
<% end %>

Then in my partials I can just do:

<%= turbo_frame_tag dom_id(gallery_image) do %>
    <div class="image-viewer">
      <div class="gallery_image__actions_container data-visible-to-admin">
        ...stuff I only want admin to see
      </div>

      ...stuff anyone should see
    </div>
<% end %>
1 Like