Pundit::NotAuthorizedError flash message isn't shown when using Turbo

Hi,

I’m integrating Pundit into a project for authorization and its documentation suggests adding this to your project as a general fallback plan when authorization fails:

class ApplicationController < ActionController::Base
  include Pundit::Authorization

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    flash[:alert] = "You are not authorized to perform this action."
    redirect_back(fallback_location: root_path)
  end
end

This works great when you’re not dealing with Turbo Frames and Turbo Streams, but when inside of a Turbo Frame or using Turbo Streams I noticed that the flash message will never get shown. I’ve also tried using flash.now[:alert] and it results in the same outcome of the flash message not being shown.

Any thoughts on how to workaround this without having to disable Turbo for these actions? In my exact use case I’m operating inside of a nested frame (think tabbed navigation and inside of each tab there’s fully self contained frames). Disabling frames and streams isn’t an option here.

Thanks!

It seems as though in this scenario you’d want to break out of the frame and render the entire root_path html?

Ideally I wouldn’t want to break out of the frame, I’d like to stay on the page / frame and show a flash message “now” mentioning they can’t perform that action.

In a perfect world it would work something like this:

  def user_not_authorized
    respond_to do |format|
      format.html do
        flash[:alert] = "You are not authorized to perform this action."
        redirect_back(fallback_location: root_path)
      end
      format.turbo_stream do
        flash.now[:alert] = "You are not authorized to perform this action."
        turbo_stream.prepend "flash", partial: "shared/flash"
      end
    end
  end

With the above code, on authorization failure no alert message gets shown and the html format gets executed (I verified that by printing a message to the terminal in that block). This makes sense because Pundit doesn’t send the request as a turbo_stream so that format never gets a chance to execute.

I was hoping someone figured out a way to patch this until Pundit is modified to support it out of the box.

Hi, what i did:

  def user_not_authorized(exception)
    policy = exception.policy
    policy_name = exception.policy.class.to_s.underscore
    error_key = policy.error_message_key || exception.query
    error_message_18n_params = policy.error_message_18n_params || {}

    msg = t "#{policy_name}.#{error_key}", scope: "pundit", default: :default, **error_message_18n_params
    respond_to do |format|
      format.html { redirect_to(request.referrer || root_path, flash: { alert: msg } )}
      format.turbo_stream {redirect_to(request.referrer || root_path, flash: { alert: msg } )}
    end
  end

Better than nothing, refresh but flash.