Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't make AnyCable work with Devise in development #127

Closed
davidalejandroaguilar opened this issue May 26, 2020 · 8 comments
Closed

Can't make AnyCable work with Devise in development #127

davidalejandroaguilar opened this issue May 26, 2020 · 8 comments

Comments

@davidalejandroaguilar
Copy link

davidalejandroaguilar commented May 26, 2020

I've been trying to make this work all day. Sorry if I missed anything, my brain melted now.

Tell us about your environment

Ruby version:
2.7.1

Rails version:
Rails 6.0.3.1

anycable gem version:
1.0.0.preview2

anycable-rails gem version:
1.0.0.preview2

grpc gem version:
1.28.0

stimulus_reflex gem version
3.2.1

anycable-go version
1.0.0.preview1-5ff1003

What did you do?

  • I'm trying to set up AnyCable in development with Devise.
  • I'm using Stimulus Reflex.
  • ActionCable works with or without Devise.
  • AnyCable works without Devise (with an error on logs). ⚠️
  • AnyCable doesn't work with Devise.
  • I created a repo that reproduces the issue.
    • Create a User record.
    • Start ./bin/webpack-dev-server
    • Start rails s
    • Start bundle exec anycable
    • Start anycable-go --host=localhost --port=3334

I'm following these basic steps / configurations:

  • Setup cable.yml
development:
  adapter: any_cable

test:
  adapter: test

production:
  adapter: any_cable
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: anycable_production
  • Setup anycable.yml
default: &default
  persistent_session_enabled: true
  redis_channel: '__anycable__'

development:
  <<: *default

production:
  <<: *default
  • Setup development.rb
    cache_store is needed for Stimulus Reflex.
config.session_store :cache_store
config.action_cable.url = "ws://localhost:3334/cable"
  • Setup Warden::Manager middleware:
    Source
AnyCable::Rails::Rack.middleware.use Warden::Manager do |config|
  Devise.warden_config = config
end
  • Setup Connection in various ways, none of them work:

Works with ActionCable, raises uncaught throw :warden with AnyCable.
Source

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

    def connect
        user_id = cookies.encrypted[:user_id]
        return reject_unauthorized_connection if user_id.nil?

        user = User.find_by(id: user_id)
        return reject_unauthorized_connection if user.nil?

        self.current_user = user
    end
  end
end

Raises undefined method loaded? for nil:NilClass.
Source
Also these didn't work.

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

    def connect
      self.current_user = find_verified_user || reject_unauthorized_connection
    end

    protected

    def find_verified_user
      app_cookies_key = Rails.application.config.session_options[:key] ||
        raise("No session cookies key in config")

      env["rack.session"] = cookies.encrypted[app_cookies_key]
      Warden::SessionSerializer.new(env).fetch(:user)
    end
  end
end

RPC server shows instance of IO needed
Source

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

    def connect
      self.current_user = find_verified_user || reject_unauthorized_connection
    end

    protected
    def find_verified_user
      app_cookies_key = Rails.application.config.session_options[:key] ||
        raise("No session cookies key in config")

      env['rack.session'] = Marshal.load(Redis.current.get(cookies[app_cookies_key]))
      Warden::SessionSerializer.new(env).fetch(:user)
    end
  end
end

raises uncaught throw :warden

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    def connect
      true
    end
  end
end
  • Start rpc server:
bundle exec anycable
  • Start ws server
anycable-go --host=localhost --port=3334

What did you expect to happen?

  • Expected connection to be established successfully with Devise.

What actually happened?

  • Various errors, defined above in the "Setup Connection section".
  • Without Devise, Stimulus Reflex works but logs a warning:
Failed to commit session! undefined method `commit_session' for nil:NilClass /Users/davidramos/.asdf/installs/ruby/2.7.0/lib/ruby/gems/2.7.0/gems/stimulus_reflex-3.2.1/lib/stimulus_reflex/channel.rb:75:in `commit_session'

Any help would be greatly appreciated.
Referencing this issue here just because it's a good collaboration bridge between Stimulus Reflex and AnyCable.

@palkan
Copy link
Member

palkan commented May 26, 2020

Do you set secret_key_base explicitly for development env? See this issue: #125

That could be the reason.

Failed to commit session! undefined method `commit_session'

This has been fixed in master, try gem 'anycable-rails', GitHub: 'anycable/anycable-rails'.

@palkan palkan added the awaiting response label May 26, 2020
@davidalejandroaguilar
Copy link
Author

davidalejandroaguilar commented Jun 2, 2020

Hey @palkan , thank you very much for your answer and time. I'm sorry I didn't respond earlier; it's been a busy week!

  • I didn't set secret_key_base explicitly. I haven't changed it at all. It's the same that was created by Rails.

  • I don't think I'm having the same problem as the issue you linked, since I can see the value of the encrypted cookie on ApplicationCable::Connection#connect.

  • The repo I linked was created without any other changes than the ones described here and still presents the same error.

  • About the commit_session error, I don't worry about it since I will be using Devise. But I just want you to know that the sample repo is using anycable-rails's master branch and the error was there. The specific versions I'm using are listed on top.

Please let me know if there's anything else I can do to help.

@palkan palkan added investigation required and removed awaiting response labels Jun 2, 2020
@palkan
Copy link
Member

palkan commented Jun 2, 2020

Oh, I didn't notice the link to the reproduction repo in the description. Will check soon!

@wolfovich
Copy link

wolfovich commented Jun 2, 2020

With anycable-rails 1.0.0 you can fetch user in one line

def find_verified_user
  Warden::SessionSerializer.new(env).fetch(:user)
end

@palkan
Copy link
Member

palkan commented Jun 2, 2020

OK, I checked the app, and found the following.

First, it didn't work for me at all and raised InvalidAuthenticityToken when I tried to log in or sign up. Switching to :cookie_store for sessions fixed this.

Then I tried to run it with master version of both anycable and anycable-rails.

This version works fine:

def connect
   user_id = cookies.encrypted[:user_id]
   return reject_unauthorized_connection if user_id.nil?

   user = User.find_by(id: user_id)
   return reject_unauthorized_connection if user.nil?

   self.current_user = user
end

The canonical version from the docs also works fine:

def connect
  self.current_user = env["warden"].user(:user) || reject_unauthorized_connection
end

You had cache_store set to :null_store, that's what likely caused the problem.
Switching to :redis_cache_store (you need some distributed cache store for sessions, since we want to use them in different processes) also didn't break anything.

Failed to commit session! undefined method `commit_session'

This one was fixed in 24607a3

The changes I made to the app code (except from disabling some gems):

  • config/environment/development.rb
Rails.application.configure do
   # Settings specified here will take precedence over those in config/application.rb.
 
-  config.session_store :cache_store
+  config.session_store :cookie_store, key: '_any_session'
  • Added config/secrets.yml with explicit secret_key_base value.

@palkan
Copy link
Member

palkan commented Jun 10, 2020

@davidalejandroaguilar Have you figure this out?

@palkan palkan added the awaiting response label Jun 10, 2020
@davidalejandroaguilar
Copy link
Author

davidalejandroaguilar commented Jun 11, 2020

Hey @palkan , thanks for checking the review app. I really appreciate your efforts here! Week's still crazy so that's why my response is delayed, apologies for that.

Few comments on the sample app:

  • The cache_store was set to :memory_store and if caching was disabled, it defaulted to :null_store. I did have caching enabled, sorry I wasn't more clear on this.
  • Hmm... I just boot up the app and don't receive an InvalidAuthenticityToken, that's strange.

Other than that, yep, that line fixes it! 🎉 . It makes sense now that both processes would need the cookie.

config.session_store :cookie_store, key: '_any_session'

I guess then Stimulus Reflex is not compatible with AnyCable? I had that set up per their docs:

Starting with v2.2.2 of StimulusReflex, support for the Rails default session storage mechanism cookie_store has been temporarily dropped. The stimulus_reflex:install script will now set your session storage to be :cache_store in your development environment if no value has been set.

I'm going to close this issue now. Thank you again for your time and patience on this. Hopefully deployment will go smoothly now! 🤞

@palkan
Copy link
Member

palkan commented Jun 11, 2020

I guess then Stimulus Reflex is not compatible with AnyCable?

AnyCable is compatible with Stimulus Reflex. Though it won't work with memory store, 'cause it's not distributed, i.e., lives within a particular process. But with AnyCable you have two processes. Hence, you need a distributed cache, such as Redis Cache Store (and I tried it with the repro app — worked fine).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants