Devise: Warden Key Missing - Action Cable

Created on 13 Mar 2017  ·  16Comments  ·  Source: heartcombo/devise

I've got an up to date Rails 5 app running Devise, and Action Cable. I can authorize the connection just fine, that is not the problem. The issue is using current_user helper within a notification partial that's pushed out using Action Cable. It's causing an error because no middleware is exec in Action Cable.

TLDR;

  • model record contains an after action that creates a notification record
  • notifications are background jobs, thus they do NOT stem from a controller
  • current_user helper causing error of missing warden key when used in a partial broadcasted with action cable

This sums it up better than I can. Here's the accompanying link: https://evilmartians.com/chronicles/new-feature-in-rails-5-render-views-outside-of-actions

image 2017-03-13 at 9 33 55 am

I've tried a few work arounds and haven't had much luck, and I'm definitely not in the mindset of monkey patching. Any thoughts?

Discussion Needs more info

Most helpful comment

I'm bumping this again after researching on Google how to fix this error, and coming across my own bug report from back in March. I realize you don't get paid to maintain this gem but if you have any thoughts, I'd love to hear them.

All 16 comments

I'm bumping this again after researching on Google how to fix this error, and coming across my own bug report from back in March. I realize you don't get paid to maintain this gem but if you have any thoughts, I'd love to hear them.

Any idea on how to get this fixed? I have tried many things that did not work. Having issue with user_signed_in? as well. Any help or idea on how to fix this will be appreciated.

Hi @bearded-avenger, thank you for reporting this. Sorry for taking this long to reply, I've started to looking into the issues recently and there were a lot of them.
Unfortunately, I haven't worked with Action Cable before, so I'm not sure what would be an ideal solution for this problem.
I'm not sure if I'm missing something. The article suggests to mock a rack env, but if we do, we won't have any real values on it, right?
Let's say that we make #current_user handle the case were warden isn't initialized: what would the method return? nil?

I'm closing this issue because it has not had recent activity.
If you're still facing this on the latest version, please open a new one with all the information requested in the template.

Thank you!

Actually, this is an issue that happens when you want to render a partial outside of your actions...Like in a job, and your partial uses devise helpers (current_user, user_signed_in? etc). This is similar to the following issue: https://github.com/plataformatec/devise/issues/4271

This is still an issue, and yeah the above bug is just like it.

Our app uses notifications. Each notification is in a partial. Each partial has something like current_user.has_read_notification?. Notifications are delivered in real time with action cable. With the way things are now, as you mentioned, there's no way to get a handle on current_user. Passing the user through the partial and doing a lookup isnt an option.

Ideally, we have access to current_user in partials rendered outside of the scope of the app.

@bearded-avenger I just want to know what the ideal solution would be in order to tackle this. I don't have previous experience with action cable or Application.renderer, so I'm a little lost here.
Since no rack middlewares are called, I don't see how we could access the current user. The blog post mentioned above says we should "mock a request". But if that's the case, current_user would always return nil, right? Am I missing something here?

@tegon thanks for popping in and looking into this, I really do appreciate your time. I'm not well versed enough in devise or middleware to give you a good answer to that question yet, but it sounds about right. Perhaps someone else reading this may have some thoughts.

@bearded-avenger oh, ok. Thanks for your reply.

The thing is: we use Warden for authentication. It is a rack middleware that is called in a request and reads the current session to define whether a user is authenticated or not. By reading the previous comments, it seems like in this case, no rack middlewares are called - which is why you're getting this error message. So I'm not sure how we could handle this without any middlewares being called.

I'm going to reopen this to see if anyone else can help.

[ActiveJob] [CommentBroadcastJob] [29558acd-658a-4a3a-b870-f447881740b8] Error performing CommentBroadcastJob (Job ID: 29558acd-658a-4a3a-b870-f447881740b8) from Async(default) in 34.14ms: 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.
If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the Devise::Test::ControllerHelpers module to inject the request.env['warden'] object for you.):

I cannot find any solution for it.
any updates?

I've run into this as well. I'm trying to bring the notifications feature from this app into my own:
https://github.com/gorails-screencasts/gorails-episode-125/

I'm running ActionCable on a separate server (using docker compose) and Apartment for multi-tenancy with subdomain scoped cookies. My action-cable-url was set to ws://lvh.me:28080. It needs the subdomain in there to access the env['warden'] key from a ws:// request. It can be fixed with something like this in your application layout:

- if current_account.present?
  -# TODO account for wss protocol for production (wss)
  %meta{name: "action-cable-url", content: "ws://#{current_account.subdomain}.lvh.me:28080"}
- else
  = action_cable_meta_tag

Or something more graceful that works with the existing action_cable_meta_tag helper.

Hi there,

I'm running a project that uses:

  • Ruby 2.3.0
  • Rails 5.0.1
  • Actioncable 5.0.6

We are using Action Cable, and for authentication using Devise and Warden, we have the following code in the app/channels/connection.rb file.

module ApplicationCable
  class Connection < ActionCable::Connection::Base
  identified_by :current_user

 def connect
    self.current_user = find_verified_user
    logger.add_tags 'ActionCable', current_user.email
  end

  protected

  def find_verified_user
    if verified_user == env['warden'].user
     verified_user
    else
      reject_unauthorized_connection
    end
  end
end

Hope it helps. Regards.

I'm closing this since it looks like the way to is to manually pass the current user to the views. Because we rely on Warden there must be a rack request in order for it to work.

I was facing the same problem, when I tried to use ApplicationController.render to render the partial view that I want to pass to the action cable.

I've got the solution from the article linked below, but this solution makes sign_in_count increase whenever rendering a partial view using ApplicationController.render_with_signed_in_user.

 def self.render_with_signed_in_user(user, *args)
   ActionController::Renderer::RACK_KEY_TRANSLATION['warden'] ||= 'warden'
   proxy = Warden::Proxy.new({}, Warden::Manager.new({})).tap{|i| i.set_user(user, scope: :user) }
   renderer = self.renderer.new('warden' => proxy)
   renderer.render(*args)
 end

https://www.stefanwienert.de/blog/2016/04/05/using-rails-5-new-renderer-with-authentication-gems-like-clearance-or-devise/

It can be happening when you try to get Devise current_user in some part of code being managed by action cable. like a background job to render comment or something else. you can resolve it by using something like following in your controller, since you cant access warden at a model or job level(according to my limited knowledge): (I call a job right after creation of my comment in CommentsController create action, which calls a private method to render a comment partial containing edit and delete button, for which current_user was required)

 def create
@product = Product.find(comment_params[:product_id])
@comment = @product.comments.build(comment_params)
@comment.save!

gon.comment_id = @comment.id
gon.comment_user_id = @comment.user_id

ActionCable.server.broadcast "chat", comment: render_comment

render :create, layout: false
end


def render_comment
  CommentsController.renderer.instance_variable_set(:@env, {"HTTP_HOST"=>"localhost:3000", 
    "HTTPS"=>"off", 
    "REQUEST_METHOD"=>"GET", 
    "SCRIPT_NAME"=>"",   
    "warden" => warden})

  CommentsController.render(
    partial: 'comments/comment_detail',
    locals: {
      product: @product,
      comment: @comment
    }
  )
end

this will help you resolve warden issue, if you have used devise's current_user in that partial, it will give you the commentor user (as it should since that user initiated the rendering of partial). Now to solve this, if you have a front end framework you might need to fetch the current user from cookies in order to restrict some actions like edit/delete. but if you are working in pure rails the solution I came across is that you have to make a hidden field in the dom having current users id, and you will fetch that id for comparison in a script. you might need to access rails variables in javascript, for that you can use GON gem. I know this answer might contain much more than asked but I've searched alot and no where I found a satisfactory solution to this problem, feel free to discuss.

env['warden'].user

I found this approach on a lot of places, but for me env['warden'].user is nil.

UPDATE:

I had multiple models configured for authorization, so https://stackoverflow.com/a/49884788/4738391 solved the issue.

Was this page helpful?
0 / 5 - 0 ratings