How can I prefix cookies with __Host or __Secure? - ruby-on-rails

I am trying to add prefix to session cookies in rails 6.0.3 app but couldn't find a way to get it done. I have tried adding key to options hash in session store but it didn't help and breaks my application. I am using auth-logic gem for authentication, I find no way to get it done gracefully but hopping on that there is some way.
conf/initalizers/session_store.rb
opts = {}
if Rails.configuration.host == "myapplication.com"
opts = {expire_after: 2.months, domain: :all}
end
unless Rails.env.test?
opts[:secure] = true
opts[:same_site] = :none
end
opts[:key] = '__Host-'
Rails.application.config.session_store :active_record_store, **opts
Attached is the screenshot of github cookies. I want my session headers as like in the image (prefixed with __Host-).

As per your link...
Cookies with the __Host- prefix must have a path of /
(meaning any path at the host) and must not have a Domain attribute.
So I would presume you need to remove the domain attribute and add the path. e.g.
opts = {}
if Rails.configuration.host == "myapplication.com"
opts = {expire_after: 2.months}
end
unless Rails.env.test?
opts[:secure] = true
opts[:same_site] = :none
opts[:path] = '/'
end
opts[:key] = '__Host-'
Rails.application.config.session_store :active_record_store, **opts

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.

cache auth0 public key

I'm using auth0 for authentication and using one of their provided methods (copied below) to confirm the jwt token. The solution they provide hits their service on every request to my server, which is making the requests take up to 1 second to complete.
I'm trying to cache the public key that is generated by the method they provide but having no luck. At first I thought I can store it in Rails.cache but then realized the method creates an OpenSSL object, not a string, and when I try Rails.cache.write('KEY', openSslObject) and access it with Rails.cache.fetch('KEY') I'm getting nil returned
I also tried to use a block with the rails cache fetch:
cached_jwks_hash = Rails.cache.fetch("JWKS_HASH", expires_in: 10.hours) do
get_jwks_hash
end
but still get nil
The get_jwks_hash method below returns the following: {"key"=>#<OpenSSL::PKey::RSA:0x007fe29c545ef8>}
what would be the best way to cache this data? is it possible to store this in a variable in memory?
def verify(token, jwks_hash)
JWT.decode(token, nil,
true, # Verify the signature of this token
algorithm: 'RS256',
iss: "https://#{ENV["AUTH0_DOMAIN"]}/",
verify_iss: true,
aud: ENV["AUTH0_API_AUDIENCE"],
verify_aud: true) do |header|
jwks_hash[header['kid']]
end
end
def get_jwks_hash
jwks_raw = Net::HTTP.get URI("https://#{ENV["AUTH0_DOMAIN"]}/.well-known/jwks.json")
jwks_keys = Array(JSON.parse(jwks_raw)['keys'])
Hash[
jwks_keys
.map do |k|
[
k['kid'],
OpenSSL::X509::Certificate.new(
Base64.decode64(k['x5c'].first)
).public_key
]
end
]
end
Ok, so after doing research, I see the problem. The key is not being saved because the rails.cache.fetch method is utilizing a null_store which doesnt actually store any data. In my config/environments/development file theres the following code:
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true
config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=172800'
}
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
and since I dont have a tmp/caching-dev.txt file, the null_store is being used. The second to last line can be updated to read
config.cache_store = :memory_store or whatever kind of store you like

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:

session cookie httponly false rails 3.1

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

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