Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

rails-session_cookie

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rails-session_cookie

  • 0.3.0
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

Rails::SessionCookie

Fast, loosely coupled requests specs for a cookie-authenticated application.

Gem Version Codecov

Key goals:

  • how to login under any user in request tests quickly
  • how to speed up capybara, selenium tests
  • how to login under any user in production using rails console

Why

Probably, you might have seen a lot code like this:

# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store

# authenticating method (maybe Devise or whatever)
session[:current_user_id] = current_user.id

# somewhere in helper for request specs
def login(current_user)
  post '/login', auth_data(current_user)
end

# now every request spec is calling login request
RSpec.describe 'User interface', type: :request do
  let(:user) { create :user }

  before do
    login(user)
  end

  it 'shows private data' do
    get '/dashboard'
  end
end

In a usual user-driven application this tightly couples all request specs, which require authentication, to the login process. If it fails - everything fails. If it's not blazing fast - it slows the whole suite down.

One may move to token-based authentication, especially when having API. That's reasonable and nice. But HTTP is stateless, really we don't need to do several requests, we can think about a session cookie as a token passed in a special header!

You can easily pass headers in tests, the only hard thing is getting the cookie value. Rails may change how a state is serialized into the session cookie. It can be encrypted or not, marshalled (an old story for rails-3 legacy) or JSONed. Long story short: only rails knows how to generate cookie from data.

This gem replaces your usual process of getting session cookie with the simplest rack app utilizing 2 rails middlewares. Rails is modular, that's cool :)

Installation

# Gemfile
gem 'rails-session_cookie', group: :test

Usage in requests specs

# spec_helper.rb
require 'rails/session_cookie'

def login(current_user)
  # depending on Rails version and session configuration this looks like "cookie_store_key=data--digest; path=/; HttpOnly"
  raw_session_cookie = Rails::SessionCookie::App.new(current_user_id: current_user.id).session_cookie

  # note, it's raw, not `<<`
  cookies.merge(raw_session_cookie)
end

# ...everything else the same

Now you can cache raw_session_cookie globally or per-thread depending on current_user_id to get things even faster!

You can also use the raw_session_cookie directly like this:

get "/", {}, { "HTTP_COOKIE" => raw_session_cookie }

Strictly speaking, you may cache Set-Cookie response header from /login URL to achieve same speed (but not coupling ;) However, never saw this in practice, and consider caching of requests in before-phase bad. YMMV.

Advanced usage

If you need more sophisticated logic:

auth_app = proc { |env|
  # do your magic
  env[Rails::SessionCookie::RACK_SESSION].merge!(context)
  [200, {}, []]
}
raw_session_cookie = Rails::SessionCookie::App.new(auth_app).session_cookie

Of course, you can just make use (and reuse!) of as many procs as you wish.

This effectively achieves the effect as this PR#18230, which allows session mutation in a less invasive way in regard to Rails itself ;)

Warden / Devise

Getting session cookie is dead-simple, just get the cookie this way:

raw_session_cookie = Rails::SessionCookie::WardenApp.new(user).session_cookie

Feature tests using Capybara

Get the cookie as described above according to your setup, and assign this way:

Capybara.current_session.driver.browser.set_cookie raw_session_cookie

TODO: Only tested with :rack_test driver!

Login under any devise user in rails production

If you're in production rails console:

Rails::SessionCookie::WardenApp.new(User.last).session_cookie

If you're on remote/developer instance:

# take values from production console:
secret_key_base = Rails.application.env_config['action_dispatch.secret_key_base']
data = User.serialize_into_session(user) # [[user.id], user.encrypted_password[0,29]]
opts = Rails.application.config.session_options

# in remote rails console:
key = "warden.user.user.key" # "warden.user.#{scope}.key"
Rails::SessionCookie::App.new({ key => data }, opts).session_cookie(secret_key_base: secret_key_base)

Then inject the value inside cookies in browser devtools. This another friendly reminder, why you need to keep your SECRET_KEY_BASE secure!

Benchmarks

NOTE: Sometimes devise's sign_in is still faster than SessionCookie (a little though), because Warden uses an ugly hack, in my opinion, to support test-mode authentication.

But, still, in average performance of this gem is not worse if used with user_id->cookie caching Besides, authentication becomes as transparent as possible and should increase readability if you understand HTTP session cookies principles.

$ appraisal rails-5.1-warden rspec -t performance spec/benchmarks/feature_spec.rb
# or just
$ BUNDLE_GEMFILE=gemfiles/rails_6.0_warden.gemfile bundle exec rspec

Speed using capybara in feature test
  correctness of
    SessionCookie
      is correct
    Devise Helpers
      are correct
  against Devise::Test::Helpers
    is obviously slower separately
    is not slower than devise helpers if using cache and executing multiple specs in a suite

Warming up --------------------------------------
devise sign_in
                        70.000  i/100ms
session cookie
                        70.000  i/100ms
session cookie (no cache)
                        62.000  i/100ms
Calculating -------------------------------------
devise sign_in
                        700.554  (± 5.3%) i/s -      3.500k in   5.011356s
session cookie
                        686.868  (± 4.7%) i/s -      3.430k in   5.005542s
session cookie (no cache)
                        611.439  (± 4.9%) i/s -      3.100k in   5.083986s

Comparison:
devise sign_in           :      700.6 i/s
session cookie           :      686.9 i/s - same-ish: difference falls within error
session cookie (no cache):      611.4 i/s - 1.15x  slower

But when it comes with comparison to a simple custom authentication (see spec/support/rails_app.rb), this gem is several times faster! (custom action checks password, hits database, request touches the whole rails middleware stack)

$ appraisal rails-5.1-warden rspec -t performance spec/benchmarks/request_spec.rb

Speed using custom sign-in in request test
  correctness of
    SessionCookie
      is correct
    usual session controller
      is correct
  against custom sign in route
    is faster separately without cache

Warming up --------------------------------------
custom sign in
                         1.000  i/100ms
session cookie
                         1.759k i/100ms
session cookie (no cache)
                       482.000  i/100ms
Calculating -------------------------------------
custom sign in
                         11.219  (± 0.0%) i/s -     57.000  in   5.082143s
session cookie
                         17.573k (± 2.0%) i/s -     87.950k in   5.006754s
session cookie (no cache)
                          4.714k (± 5.0%) i/s -     23.618k in   5.023448s

Comparison:
session cookie           :    17573.4 i/s
session cookie (no cache):     4714.3 i/s - 3.73x  slower
custom sign in           :       11.2 i/s - 1566.44x  slower

FAQs

Package last updated on 17 May 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc