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
Related
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
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.
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
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
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