session cookie httponly false rails 3.1 - ruby-on-rails

I'm trying to turn httponly off for use in phonegap. I'm useing rails 3.1 and devise, each of which have reported (but not documented) ways of doing this, none of which work:
# application.rb
config.session_options = { :httponly => false } # no effect
config.session = { :httponly => false } # undefined method `session='
# devise.rb
config.cookie_options = { :httponly => false } # also no effect
to test I restarted the server, deleted the existing cookie, and reloaded the page. 'Http' column was still checked in the chrome debugger.
help!

This little snippet seems to work :
Testapp::Application.config.session_store :cookie_store, key: '_testapp_session', :domain => :all, :httponly => false

As far as I can tell, this is a bug in rails. Perhaps the option got removed, but the documentation stayed. Any ideas on this would be welcome!
I spent several thorough hours with ActionPack, and couln't find any reference to such a configuration option-- but I still don't have the full picture as to how it works. Specifically, there's the cookiestore which holdes cookies and writes them to the header (and is passed :httponly => true), but I couldn't find how the session is using the store-- with vague things like the Rails SessionManage module being a proverbial ghost town.
I hacked up a middleware which does the job:
# application.rb:
config.middleware.insert_before ActionDispatch::Cookies, "UnshieldCookie" # remove httponly.
# unshielded_cookie.rb
class UnshieldCookie
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
headers['Set-Cookie'].gsub!('HttpOnly', '') if headers['Set-Cookie'].present?
[status, headers, body]
end
end

Related

Adding 'SameSite=None;' cookies to Rails via Rack middleware?

On February 4th 2020, Google Chrome will require SameSite=None; to be added to all cross-site cookies. Rails 6.1 and soon Rails 6.0 have added a same_site: :none option to the rails cookie hash:
cookies["foo"]= {
value: "bar",
expires: 1.year.from_now,
same_site: :none
}
But older Rails 5.x apps won't receive the upgrade to have access to the same_site options hash. I know the SameSite=None; cookie option can be manually added to Rails in a controller using:
response.headers["Set-Cookie"] = "my=cookie; path=/; expires=#{1.year.from_now}; SameSite=None;"
But my Rails 5.x app uses complicated cookie objects that modify cookies. Instead of breaking them apart, I would like to write Rack middleware to manually update all cookies with the SameSite=None; attribute at once.
This StackOverflow answer shows a way to cookies can be modified to update cookies within Rack Middleware:
# lib/same_site_cookie_middleware
class SameSiteCookieMiddleware
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
# confusingly, response takes its args in a different order
# than rack requires them to be passed on
# I know it's because most likely you'll modify the body,
# and the defaults are fine for the others. But, it still bothers me.
response = Rack::Response.new body, status, headers
response.set_cookie("foo", {:value => "bar", :path => "/", :expires => 1.year.from_now, same_site: :none})
response.finish # finish writes out the response in the expected format.
end
end
# application.rb
require 'same_site_cookie_middleware'
config.middleware.insert_after(ActionDispatch::Cookies, SameSiteCookieMiddleware)
How do I re-write this Rack Middleware code to manually append SameSite=None; into every existing cookie?
I was able to get all cookies to use SameSite=None by default updating rack:
gem 'rack', '~> 2.1'
use Rack::Session::Cookie,
:httponly => true,
:same_site => :none,
:secure => true,
:secret => COOKIE_SECRET.to_s()
I was able to get this to work with the following:
# frozen_string_literals: true
class SameSiteCookies
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
set_cookie_header = headers['Set-Cookie']
if set_cookie_header && !(set_cookie_header =~ /SameSite\=/)
headers['Set-Cookie'] << ';' if !(set_cookie_header =~ /;$/)
headers['Set-Cookie'] << ' SameSite=None'
headers['Set-Cookie'] << '; Secure' if env['rack.url_scheme'] == 'https';
end
[status, headers, body]
end
end
and adding to middleware with:
Rails.application.config.middleware.insert_before(ActionDispatch::Cookies, SameSiteCookies)
I had a problem with Rails 5 headers being frozen. This is similar to Carson's answer but it goes around this problem. Should work for both rails 5 < and Rails 5+.
# frozen_string_literals: true
class SameSiteCookies
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
set_cookie_header = headers['Set-Cookie']
if set_cookie_header && !(set_cookie_header =~ /SameSite\=/)
# the set cookie header variable is frozen
new_set_cookie_header = set_cookie_header.dup
new_set_cookie_header << ';' if !(set_cookie_header =~ /;$/)
new_set_cookie_header << ' SameSite=None'
new_set_cookie_header << '; Secure' if is_ssl?
headers['Set-Cookie'] = new_set_cookie_header
end
[status, headers, body]
end
private
def is_ssl?
# custom logic for my application
end
end
Insert the middleware
Rails.application.config.middleware.insert_before(ActionDispatch::Cookies, SameSiteCookies)
Update: For Rails 5.x and lower, I found the rails_same_site_cookie gem to be a good option for adding SameSite=None; to all your app's cookies. It uses middleware to do it.
The secure_headers gem lets you configure the cookie policy out of the box:
SecureHeaders::Configuration.default do |config|
config.cookies = {
secure: true, # mark all cookies as "Secure"
httponly: true, # mark all cookies as "HttpOnly"
samesite: {
none: true # mark all cookies as SameSite=lax
}
}
I used this solution to add SameSite=None to our cookies on a Rails 5 application.
Once a cookie is set you cannot modify the cookie properties like expiry, domain, path.
Browsers return only the cookie name and value once a cookie has already been set, over-riding any of the cookie property will create a new cookie. I would recommend to delete the existing cookie and create a new cookie with same name and value.
headers['Set-Cookie'] instructs the browser to create a new cookie and modifying the value in middleware gives you a very little control on the attribute value.
I have answered here how this can be achieved by modifying the Rack::Utils.set_cookie_header! method.

Setting public_file_server.headers except for some files

I use this in production.rb :
config.public_file_server.headers = {
'Cache-Control' => 'public, s-maxage=31536000, maxage=31536000',
'Expires' => "#{1.year.from_now.to_formatted_s(:rfc822)}"
}
I use public files through a cdn.mydomain.com, which is reading from www.mydomain.com and it copies the cache-control from www.mydomain.com, that I set with public_file_server.headers.
The issue is that I want some files from /public to not have those cache-control, for example for my service-worker.js
Is there a way to set those cache control only for one folder in /public for example?
The other solution would be to remove this public_file_server.headers configuration, and setting the cache control on the cdn level (I use cdn.mydomain.com/publicfile), and keeping www.mydomain.com/serviceworker without cache control, for the service worker.
But maybe there is a chance to config this at the Rails level?
I had exactly the same problem: PWA built with Rails using CDN (Cloudfront). For the assets I want to use cache headers with far future expires, but the ServiceWorker needs Cache-control: No-cache.
Because CloudFront doesn't allow to add or change headers by itself, I need a solution on the app level. After some research I found a solution in a blogpost. The idea is to set headers via public_file_server.headers and add a middleware to change this for the ServiceWorker file.
Also, you wrote maxage=, it should be max-age=.
Here is the code I use:
production.rb:
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
config.public_file_server.headers = {
'Cache-Control' => 'public, s-maxage=31536000, max-age=15552000',
'Expires' => 1.year.from_now.to_formatted_s(:rfc822)
}
if ENV['RAILS_SERVE_STATIC_FILES'].present?
config.middleware.insert_before ActionDispatch::Static, ServiceWorkerManager, ['sw.js']
end
app/middleware/service_worker_manager.rb:
# Taken from https://codeburst.io/service-workers-rails-middleware-841d0194144d
#
class ServiceWorkerManager
# We’ll pass 'service_workers' when we register this middleware.
def initialize(app, service_workers)
#app = app
#service_workers = service_workers
end
def call(env)
# Let the next middleware classes & app do their thing first…
status, headers, response = #app.call(env)
dont_cache = #service_workers.any? { |worker_name| env['REQUEST_PATH'].include?(worker_name) }
# …and modify the response if a service worker was fetched.
if dont_cache
headers['Cache-Control'] = 'no-cache'
headers.except!('Expires')
end
[status, headers, response]
end
end

Unable to set/use session variables in ruby on rails controller

Can somebody tell me the right way to set/use session variables in a ruby on rails application from scratch. I am not able to set/use a session variable in my controller between two pages. I am using CookieStore session type. This is the syntax being used to set and get session variables:
session[:test] = "testing"
#test_str = session[:test]
Let me know in case I am missing on something.
This is how my controller looks like:
class PaymentsController < ApplicationController
# GET /merchant_test
def merchant_test
session[:test] = "testing"
render :layout => false
end
# POST /post_to_mobikwik
def post_to_mobikwik
zr = Mobikwik::Request.new(params)
#mobikwik_data = zr.all_params
#test_str = session[:test]
render :layout => false
end
# POST /z_response
def z_response
zr = Mobikwik::Response.new(request.raw_post)
#checksum_check = zr.valid?
#mobikwik_post = zr.all_params
#statuscode = #mobikwik_post['statuscode']
#statusmessage = #mobikwik_post['statusmessage']
if #statuscode == "0"
#verified = zr.verified?
else
#verified = false
end
render :layout => false
end
Your session cookie settings might be wrong. You should inspect the headers of the response and see what the Set-Cookie header looks like.
It might have a wrong domain or perhaps your cookie is https only and you're on http.
The configuration is usually done in some place like config/initializers/session_store.rb
Myapp::Application.config.session_store :cookie_store, {
:key => '_appname_session_id',
:path => '/',
:domain => nil, # accepted domain, for example '.example.com'
:expire_after => nil, # the session will be expired in X seconds unless active
:secure => false, # if true, cookie is valid only on https
:httponly => true # if true, javascript can't access the cookie
}
Try this in config/initializers/session_store.rb
AppName::Application.config.session_store :cookie_store, key: '_app-name_session'
As a rule of thumb, in rails we create a method
in ApplicationController, called current_user
In this free RailsCast, you see exactly are you need:

invalid URI - How to prevent, URI::InvalidURIError errors?

I got the following back from delayed_job:
[Worker(XXXXXX pid:3720)] Class#XXXXXXX failed with URI::InvalidURIError: bad URI(is not URI?): https://s3.amazonaws.com/cline-local-dev/2/attachments/542/original/mac-os-x[1].jpeg?AWSAccessKeyId=xxxxxxxx&Expires=1295403309&Signature=xxxxxxx%3D - 3 failed attempts
The way this URI comes from in my app is.
In my user_mailer I do:
#comment.attachments.each do |a|
attachments[a.attachment_file_name] = open(a.authenticated_url()) {|f| f.read }
end
Then in my attachments model:
def authenticated_url(style = nil, expires_in = 90.minutes)
AWS::S3::S3Object.url_for(attachment.path(style || attachment.default_style), attachment.bucket_name, :expires_in => expires_in, :use_ssl => attachment.s3_protocol == 'https')
end
That being said, is there some type of URI.encode or parsing I can do to prevent a valid URI (as I checked the URL works in my browser) for erroring and killing delayed_job in rails 3?
Thank you!
Ruby has (at least) two modules for dealing with URIs.
URI is part of the standard library.
Addressable::URI, is a separate gem, and more comprehensive, and claims to conform to the spec.
Parse a URL with either one, modify any parameters using the gem's methods, then convert it using to_s before passing it on, and you should be good to go.
I tried ' open( URI.parse(URI.encode( a.authenticated_url() )) ' but that errord with OpenURI::HTTPError: 403 Forbidden
If you navigated to that page via a browser and it succeeded, then later failed going to it directly via code, it's likely there is a cookie or session state that is missing. You might need to use something like Mechanize, which will maintain that state while allowing you to navigate through a site.
EDIT:
require 'addressable/uri'
url = 'http://www.example.com'
uri = Addressable::URI.parse(url)
uri.query_values = {
:foo => :bar,
:q => '"one two"'
}
uri.to_s # => "http://www.example.com?foo=bar&q=%22one%20two%22"

How can I make cookies secure (https-only) by default in rails?

In a Rails controller, I can set a cookie like this:
cookies[:foo] = "bar"
And specify that the "secure" (https-only) flag be on like this:
cookies[:foo, :secure => true] = "bar"
:secure is false by default. How can I have cookies be secure by default, application-wide?
This is on Rails 2.3.8
There's no need to monkeypatch ActionController/ActionDispatch, and force_ssl has side effects (e.g. when behind an ELB).
The most straightforward way to achieve secure cookies is to modify config/initializers/session_store.rb:
MyApp::Application.config.session_store(
:cookie_store,
key: '_my_app_session',
secure: Rails.env.production?
)
starting with rails 3.1, according to the rails security guide, you can simply set the following in your application.rb:
config.force_ssl = true
this forces the cookie to be sent over https only (and I assume everything else, too).
Thanks #knx, you sent me down the right path. Here's the monkeypatch I came up with, which seems to be working:
class ActionController::Response
def set_cookie_with_security(key, value)
value = { :value => value } if Hash != value.class
value[:secure] = true
set_cookie_without_security(key, value)
end
alias_method_chain :set_cookie, :security
end
What do you think?
Quick and dirty solution: i think it is possible by modifying []= method in action pack cookies module (actionpack/lib/action_controller/cookies.rb)
from:
def []=(name, options)
if options.is_a?(Hash)
options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
options["name"] = name.to_s
else
options = { "name" => name.to_s, "value" => options }
end
set_cookie(options)
end
to:
def []=(name, options)
if options.is_a?(Hash)
options.merge!({:secure => true})
options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
options["name"] = name.to_s
else
options = { "name" => name.to_s, "value" => options }
end
set_cookie(options)
end
# session only available over HTTPS
ActionController::Base.session_options[:secure] = true
You should look at the rack-ssl-enforcer gem. I was just looking for a clean answer to this and it solves the problem independent of which version of Rails you're on, plus it's extremely configurable.
You can do this as mentioned in some of the above answers (use secure option in the config/initializers/session_store.rb file):
MyApp::Application.config.session_store :cookie_store, key: '_my_app_session',
secure: Rails.env.production?
which will only secure the session cookie, but other cookies will not be secure.
If you want to secure all the cookies in your Rails app by default, you can use the secure_headers gem. Just add the secure_headers gem to your Gemfile, bundle install the gem and create a config/initializers/secure_headers.rb file with this content:
SecureHeaders::Configuration.default do |config|
config.cookies = {
secure: true, # mark all cookies as "Secure"
}
end
This will make all the cookies secure in your Rails app by default.
You can also add these recommended configurations and set the httponly and samesite options as well:
SecureHeaders::Configuration.default do |config|
config.cookies = {
secure: true, # mark all cookies as "Secure"
httponly: true, # mark all cookies as "HttpOnly"
samesite: {
lax: true # mark all cookies as SameSite=lax
}
}
end

Resources