Skip to content

Commit

Permalink
Support Signed and Encrypted Cookie Storage (#27)
Browse files Browse the repository at this point in the history
* Support Signed and Encrypted Cookie Storage

With the `:signed` option passed into the Redis session store, you can
now ensure that sessions will be set up in the browser using a
signed/encrypted cookie. This prevents user tampering by changing their
session ID or the data within the cookie.

Closes #21
  • Loading branch information
tubbo authored Sep 17, 2019
1 parent 3b75dab commit 6c3af02
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 8 deletions.
30 changes: 23 additions & 7 deletions lib/action_dispatch/middleware/session/redis_store.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# frozen_string_literal: true

require 'redis-store'
require 'redis-rack'
require 'action_dispatch/middleware/session/abstract_store'

module ActionDispatch
module Session
# Session storage in Redis, using +Redis::Rack+ as a basis.
class RedisStore < Rack::Session::Redis
include Compatibility
include StaleSessionCheck
Expand All @@ -17,18 +20,31 @@ def initialize(app, options = {})

private

def set_cookie(env, session_id, cookie)
if env.is_a? ActionDispatch::Request
request = env
else
request = ActionDispatch::Request.new(env)
end
request.cookie_jar[key] = cookie.merge(cookie_options)
def set_cookie(env, _session_id, cookie)
request = wrap_in_request(env)
cookie_jar(request)[key] = cookie.merge(cookie_options)
end

def get_cookie(request)
cookie_jar(request)[key]
end

def wrap_in_request(env)
return env if env.is_a?(ActionDispatch::Request)
ActionDispatch::Request.new(env)
end

def cookie_options
@default_options.slice(:httponly, :secure)
end

def cookie_jar(request)
if @default_options[:signed]
request.cookie_jar.signed_or_encrypted
else
request.cookie_jar
end
end
end
end
end
2 changes: 1 addition & 1 deletion redis-actionpack.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'actionpack', '>= 5', '< 7'

s.add_development_dependency 'rake', '~> 10'
s.add_development_dependency 'bundler'
s.add_development_dependency 'bundler', '> 1', '< 3'
s.add_development_dependency 'mocha', '~> 0.14.0'
s.add_development_dependency 'minitest-rails'
s.add_development_dependency 'tzinfo'
Expand Down
23 changes: 23 additions & 0 deletions test/integration/redis_store_integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ class RedisStoreIntegrationTest < ::ActionDispatch::IntegrationTest
end
end

test "should set a signed cookie when the 'signed' option is set" do
with_test_route_set(signed: true) do
https!

get '/set_session_value'
assert_response :success

cookie = cookies.instance_variable_get('@cookies').first

assert_includes cookie.raw, '_session_id='
end
end


test "should set a http-only cookie by default" do
with_test_route_set do
get '/set_session_value'
Expand Down Expand Up @@ -238,8 +252,17 @@ class RoutedRackApp
def initialize(routes, &blk)
@routes = routes
@stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes)
@secret = SecureRandom.hex
@key_generator = ActiveSupport::CachingKeyGenerator.new(
ActiveSupport::KeyGenerator.new(@secret, iterations: 2)
)
end
def call(env)
env[ActionDispatch::Cookies::GENERATOR_KEY] = @key_generator
env[ActionDispatch::Cookies::SIGNED_COOKIE_SALT] = SecureRandom.hex
if defined? ActionDispatch::Cookies::COOKIES_ROTATIONS
env[ActionDispatch::Cookies::COOKIES_ROTATIONS] = ActiveSupport::Messages::RotationConfiguration.new
end
@stack.call(env)
end
end
Expand Down

0 comments on commit 6c3af02

Please sign in to comment.