Skip to content

ActionCable tests fail when encrypted cookie is set with options #51914

@lzell

Description

@lzell

Steps to reproduce

  1. Create app/channels/application_cable/connection.rb as a copy of section 3.1.1 of the Action Cable docs
# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    private
      def find_verified_user
        if verified_user = User.find_by(id: cookies.encrypted[:user_id])
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end
  1. Create test/fixtures/users.yml to contain the following:
lou:
  id: 1
  email: person@example.org
  1. Create a test case in test/channels/application_cable/connection_test.rb modeled after the basic example in Action Cable Connection TestCase docs
require "test_helper"

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
  test "connects with cookies" do
    cookies.encrypted["user_id"] = 1
    connect
    assert_equal 1, connection.current_user.id
  end
end
  1. Run ./bin/rails test, tests pass! ✅

  2. Change the way the cookie is set in step 3, swapping from the shorthand to longhand method of setting a cookie, per the ActionDispatch docs:

- cookies.encrypted["user_id"] = 1
+ cookies.encrypted["user_id"] = { value: 1, expires: 1.hour }
  1. Run ./bin/rails test again to observe a failing test 🛑

Executable repro

I modified this from the executable test case templates to support ActionCable. If all goes well you should be able to run with ruby failing_case.rb and see one passing test and one failing test. The second test fails on both 7.1.3.3 and 8.0.0.alpha.

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  gem "rails"
  # If you want to test against edge Rails replace the previous line with this:
  # gem "rails", github: "rails/rails", branch: "main"
end

require "action_controller/railtie"
require "action_cable"
require "active_support/time"

class TestApp < Rails::Application
  config.root = __dir__
  config.hosts << "example.org"
  config.secret_key_base = "secret_key_base"

  config.logger = Logger.new($stdout)
  Rails.logger  = config.logger
end

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

    def connect
      self.current_user_id = cookies.encrypted[:user_id]
    end
  end
end

require "minitest/autorun"

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
  tests ApplicationCable::Connection

  test "passing test - connects with cookies" do
    cookies.encrypted["user_id"] = 1
    connect
    assert_equal 1, connection.current_user_id
  end

  test "failing test - connects with cookies" do
    cookies.encrypted["user_id"] = { value: 1, expires: 1.hour }
    connect
    assert_equal 1, connection.current_user_id
  end
end

Expected behavior

ActionCable tests that rely on encrypted cookies should pass regardless of which cookie-setting syntax is used:

cookies.encrypted[:hello] = "world"
# or   
cookies.encrypted[:hello] = { value: "world", expires: 1.hour }

Actual behavior

ActionCable tests that rely on encrypted cookies fail if the developer uses the longhand form to set a cookie (which allows us to set expires, httponly, secure, or same_site options):

cookies.encrypted[:hello] = "world" # ✅ 
cookies.encrypted[:hello] = { value: "world", expires: 1.hour } # 🛑 

System configuration

Rails version: Rails 7.1.3.3
Ruby version: ruby 3.2.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions