Rails 4 ActionController::InvalidAuthenticityToken with multiple subdomains - ruby-on-rails

Before let me say that I search google and a lot of posts here at Stackoverflow, but nothing was able to give a solution for my problem. for this, I am creating a new post.
I am creating an application with two sub-domains: account.psantos.dev and app.psantos.dev.
At: config/initializers/session_store.rbI have:
Rails.application.config.session_store :cookie_store, key: '_psantos.co.ao_session', domain: 'psantos.dev'
And I have the following structure folders:
- app
-- controllers
--- app1
---- welcome_controller.rb
--- account
---- welcome_controller.rb
--views
--- layout
---- app1.html.erb
---- account.html.erb
--- app1
---- welcome
----- index.html.erb
--- account
---- welcome
----- index.html.erb
at: app/views/layout/app1.html.erb (http://app1.psantos.dev) I have the following line:
<li><%= "Logout", account_sign_out_url, method: :delete %></li>
When I click this Link (that will go to: http://account.psantos.dev), I got the following error:
ActionController::InvalidAuthenticityToken at /sign_out
ActionController::InvalidAuthenticityToken
on both layout files (app1.html.erb and account.html.erb) I have this line before <\head > tag :
<%= csrf_meta_tags %>
how can I solve this problem?
Update: relevant log
Started GET "/" for 127.0.0.1 at 2015-08-08 12:37:03 +0100 Processing
by APP1::WelcomeController#index as HTML Parameters:
{"subdomain"=>"app1"} Rendered app1/welcome/index.html.erb within
layouts/app1 (0.4ms) [1m[35mEntity Load (0.3ms)[0m SELECT
"entities".* FROM "entities" WHERE "entities"."user_token" = $1 LIMIT
1 [["user_token", "xxxxxxxxxxxxxxxxxxxx"]] Completed 200 OK in 43ms
(Views: 42.0ms | ActiveRecord: 0.3ms)
Started DELETE "/sign_out" for 127.0.0.1 at 2015-08-08 12:37:05 +0100
Processing by Account::SessionsController#destroy as HTML
Parameters: {"subdomain"=>"account"} Can't verify CSRF token
authenticity Completed 422 Unprocessable Entity in 1ms (ActiveRecord:
0.0ms)
ActionController::InvalidAuthenticityToken -
ActionController::InvalidAuthenticityToken: actionpack (4.2.3)
lib/action_controller/metal/request_forgery_protection.rb:181:in
`handle_unverified_request' actionpack (4.2.3)
lib/action_controller/metal/request_forgery_protection.rb:209:in
`handle_unverified_request' actionpack (4.2.3)
lib/action_controller/metal/request_forgery_protection.rb:204:in
`verify_authenticity_token' activesupport (4.2.3)
lib/active_support/callbacks.rb:430:in `block in make_lambda'
activesupport (4.2.3) lib/active_support/callbacks.rb:143:in `block in
halting_and_conditional' activesupport (4.2.3)
lib/active_support/callbacks.rb:502:in `block in call' activesupport
(4.2.3) lib/active_support/callbacks.rb:502:in `call'

Now it's impossible to do :delete request to subdomain.
There is an vulnerability in jquery-ujs and jquery-rails that can be used to bypass CSP protections and allows attackers to send CSRF tokens to attacker domains. This vulnerability has been assigned the CVE identifier CVE-2015-1840. Versions Affected: All. Not affected: Applications which don't use jquery-ujs or jquery-rails. Fixed Versions: jquery-rails versions 4.0.4 and 3.1.3 and jquery-ujs 1.0.4. Impact ------ In the scenario where an attacker might be able to control the href attribute of an anchor tag or the action attribute of a form tag that will trigger a POST action, the attacker can set the href or action to " https://attacker.com" (note the leading space) that will be passed to JQuery, who will see this as a same origin request, and send the user's CSRF token to the attacker domain.
This is commit to jquery-ujs:
You can read more here

Related

Rails Devise-omniauth route points to passthru instead of twitter

I am trying to implement omniauth-twitter with Devise in Ruby-on-Rails with no success.
According to the definitive article of Devise, the link
<%= link_to "Sign up with twitter", user_twitter_omniauth_authorize_path,
method: :post %>
should take the visitor to Twitter (Note I have disabled turbo site-wide with Turbo.session.drive = false and so turbo is irrelevant).
However, it just displays
Not found. Authentication passthru.
I notice the route looks wrong in the first place:
% bin/rails routes -g omni
Prefix Verb URI Pattern Controller#Action
user_twitter_omniauth_authorize GET|POST /users/auth/twitter(.:format) users/omniauth_callbacks#passthru
user_twitter_omniauth_callback GET|POST /users/auth/twitter/callback(.:format) users/omniauth_callbacks#twitter
It apparently points to Users::OmniauthCallbacksController#passthru, which is a non-existent method, hence the error?
This problem has been reported multiple times in Stackoverflow and Github (e.g., Github, SO1, SO2). A general advice seems to be using POST as opposed to GET. However, besides it is definitely POST in my case (as confirmed with a log file), I doubt if it is relevant to the routes!
What is the probable cause of this probblem and how can I solve it?
I confirm I am using OA1 API keys as opposed to Twitter OA2 (the latter is not supported by Omniauth, yet).. Also, as the doc of omniauth-twitter suggests, I confirm that "User authentication set up" is active in the Twitter Dev center to allow users to log in.
My relevant files are as follows:
# Gemfile
gem 'devise'
gem 'devise-i18n'
gem 'omniauth', '~> 2.1' #, '1.9.1'
gem 'omniauth-twitter'
gem 'omniauth-rails_csrf_protection'
# /config/initializers/devise.rb
Devise.setup do |config|
config.omniauth :twitter, 'MY_APP', 'MY_SECRET'
OmniAuth.config.logger = Rails.logger if Rails.env.development? # for debug
end
Note there are no Omniauth or Twitter-related configs in config/initializers/. Just devise.rb.
# /models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable, #...snipped...
:omniauthable, omniauth_providers: %i(twitter)
def self.from_omniauth(auth)
find_or_create_by(provider: auth.provider, uid: auth.uid) do |user|
user.display_name = auth.info.nickname.strip
user.skip_confirmation!
end
end
end
# /app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :verify_authenticity_token, only: [:twitter]
def twitter
callback_from __method__
end
def callback_from(provider)
# See User model
#user = User.from_omniauth(request.env['omniauth.auth'])
if #user.persisted?
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: provider.to_s.capitalize) if is_navigational_format?
else
session["devise.#{provider.to_s}_data"] = request.env['omniauth.auth'].except(:extra)
redirect_to new_user_registration_url(from_omniauth_callback: true)
end
end
def failure
redirect_to root_path
end
end
# /config/routes.rb
Rails.application.routes.draw do
devise_for :users, except: [:destroy],
controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
end
View (the turbo-part is meaningless, for it is globally turned off).
<%# /app/views/devise/registrations/new.html.erb >
<%= link_to t(".sign_up_with_twitter"), user_twitter_omniauth_authorize_path,
method: :post, data: { turbo: false } %>
The standard output of the server (bin/dev in Rail 7):
00:... | I, [2022-...6 #793] INFO -- : Started POST "/en/users/auth/twitter" for 127.0.0.1 at 2022-11-27 00:33:14 +0000
00:... | I, [2022-...4 #793] INFO -- : Processing by Users::OmniauthCallbacksController#passthru as HTML
00:... | I, [2022-...0 #793] INFO -- : Parameters: {"authenticity_token"=>"[FILTERED]", "locale"=>"en"}
00:... | D, [2022-...0 #793] DEBUG -- : Rendering text template
00:... | I, [2022-...7 #793] INFO -- : Rendered text template (Duration: 0.0ms | Allocations: 10)
00:... | I, [2022-...9 #793] INFO -- : Completed 404 Not Found in 4ms (Views: 2.5ms | ActiveRecord: 0.0ms | Allocations: 1170)
Version information
omniauth-rails_csrf_protection (0.1.2) → (1.0.1)
Gemfile did not specify the version and yet a lower-version was installed. I now bundle install with '~> 1.0'. The same error still remains (after server-restart).
omniauth (2.1.0)
omniauth-oauth (1.2.0)
omniauth-twitter (1.4.0)
devise (4.8.1)
rails (7.0.4)
ruby (3.1.2)
That's it. Thank you.
This isn't a full answer, but more of a suggestion of where to keep looking.
You are supposed to see (twitter) Request phase initiated. in the logs immediately following Started POST "/users/auth/twitter" (from here).
But, the Omniauth controller is instead looking for an HTML template to render (and failing to find one).
INFO -- : Started POST "/en/users/auth/twitter" for 127.0.0.1 at 2022-11-27 00:33:14 +0000
INFO -- : Processing by Users::OmniauthCallbacksController#passthru as HTML
INFO -- : Parameters: {"authenticity_token"=>"[FILTERED]", "locale"=>"en"}
DEBUG -- : Rendering text template #<--- HERE!!!!
INFO -- : Rendered text template (Duration: 0.0ms | Allocations: 10)
INFO -- : Completed 404 Not Found in 4ms (Views: 2.5ms | ActiveRecord: 0.0ms | Allocations: 1170)
This does make sense; because Users::OmniauthCallbacksController#passthru is getting the POST request as HTML, it's looking for an HTML template to render (and failing to find one).
It seems like Omniauth expects an AJAX request as JSON, not an HTML request.
A few thoughts:
Turn Turbo back on and see what the content-type of the request is when Turbo gets to decide
Ditch the user_twitter_omniauth_authorize_path in favor of intercepting the link click and forming an AJAX POST request using Stimulus (Rails 7) or Javascript directly to the path /users/auth/twitter.
The language "en" in the URL of the POST is probably coming from some other gem (devise-i18n?) That could be interfering with the POST request somehow and turning the content-type to HTML instead of JSON.

"ActionController::InvalidAuthenticityToken" error when using form_with

I have a form that looks like this:
<%= form_with(url: star.starname, method: :post, local: true) do |f| %>
<% star.availabilities.each do |avail| %>
<%= f.label avail.time_slot %>
<%= radio_button_tag(:time_slot, avail.time_slot) %> <br>
<% end %>
<%= f.submit "Create" %>
<% end %>
Immediately after form submission:
Notes:
This is occurring in an app (not an API), so sessions are important, hence CSRF protection must be left on.
The problem occurs in chrome, incognito, and safari.
I have tried logging in with different users and clearing cookies (in case it was being caused by a stale token)
Some more of the error message:
Started POST "/talljohn" for ::1 at 2020-09-16 10:06:21 +1000
Processing by StarsController#book as HTML
Parameters: {"authenticity_token"=>"P++4a+giwUBqZgCLfMwqKpMu0EGitd8zTOi5RWsnxpKlNcjiuU6hd3ebbIC/IOxlL74RJIvrq+yDuA1ZtfcvFw==", "time_slot"=>"2020-09-16 01:00:00 UTC", "commit"=>"Create", "starname"=>"talljohn"}
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms | Allocations: 655)
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
actionpack (6.0.3.2) lib/action_controller/metal/request_forgery_protection.rb:215:in `handle_unverified_request'
actionpack (6.0.3.2) lib/action_controller/metal/request_forgery_protection.rb:247:in `handle_unverified_request'
devise (4.7.2) lib/devise/controllers/helpers.rb:255:in `handle_unverified_request'
actionpack (6.0.3.2) lib/action_controller/metal/request_forgery_protection.rb:242:in `verify_authenticity_token'
activesupport (6.0.3.2) lib/active_support/callbacks.rb:428:in `block in make_lambda'
activesupport (6.0.3.2) lib/active_support/callbacks.rb:200:in `block (2 levels) in halting'
actionpack (6.0.3.2) lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
activesupport (6.0.3.2) lib/active_support/callbacks.rb:201:in `block in halting'
Update
I reverted back to the last working version of the form, which was exactly the same as above but without , local: true. Then it suddenly works! (no errors).
I thought local: true (or remote: false) simply turns off ajax form submission. So I don't understand why that would make any difference (or have anything to do with CSRF), it seems that those two aspects are unrelated and it isn't clear why these two concepts would have any affect on eachother
Update 2
I later realised that another untouched previously working form also produced this error. It had not been changed in any way. I tried it in chrome incognito, and it produced the error. Half an hour later (without changing any code) I tried it again in the same browser and it worked. This (very) strange behaviour makes me think it's something to do with sessions, cookies or caching. I will report back if I learn anything further
Update 3
After reading Sarah's solution adding protect_from_forgery prepend: true to the application controller (I tried both before and after before_action :authenticate_user!), the same error message appears in the logs, the POST request isn't actioned, but the app redirects to the home page. I.e. upon POST I see:
Can't verify CSRF token authenticity.
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms | Allocations: 444)
Started GET "/users/sign_in" for ::1 at 2020-09-17 21:08:42 +1000
Processing by Devise::SessionsController#new as HTML
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Redirected to http://localhost:3000/
Filter chain halted as :require_no_authentication rendered or redirected
Completed 302 Found in 3ms (ActiveRecord: 0.5ms | Allocations: 1900)
Update 4
I attempted to manually clear the rails fragment cache (with Rails.cache.clear ). But the result is exactly the same before/after clearing the fragment cache.
I remember running into something like this and adding protect_from_forgery prepend: true before any user authentication to the ApplicationController solved it.
TL;DR: after 2 weeks' of debugging attempts, I turned off turbolinks and the problem went away.
Aside from turning off turbolinks, another solution appears to be (mentioned here) adding this to application.js
$(document).on('turbolinks:load', function(){ $.rails.refreshCSRFTokens(); });
Previous answer
The issue kept reemerging. I have tried the following six things but it still hasn't fixed it
1. Clear the fragment cache
Rails.cache.clear (warning because it clears the cache, it will also remove things like sidekiq jobs etc). This will remove the stale token and refreshing the app in the browser will return things to normal, and the form should submit (a simple 'resubmit' won't work, so go back to the form page, refresh, then submit and it should work)
2. Hard refresh page
Press cmd + opt + j to bring up the developer console, then right click on refresh and select 'Empty Cache and Hard Reload'
3. Delete site cookies
Right click on the tiny icon to the immediate left of the url (it will be a lock if using https, or the letter 'i' if using http). Go into each of categories listed (e.g. 'Cookies', 'Site Settings' etc) and delete them all
4. Delete cookies for other urls that point to the same site
For example, if your site is www.example.com, and it's hosted on heroku at www.example.herokuapp.com, then delete cookies for that second url as well
5. Delete cookies for localhost
I deleted localhost cookies just to be sure
6. Testing in completely isolated instances of chrome
For me, the solution was to switch to a more specific Cookie Strategy:
# config/initializers/new_framework_defaults_6_1.rb
# Before: i've used None, but this leads to broken Cookies somehow. Strict or Lax seems to work.
Rails.application.config.action_dispatch.cookies_same_site_protection = :strict

Rails CSRF issue on receiving POST request from API

I am working on Rails application which interacts with the API. Now there is a step that the API will send the POST request to the application and I tried saving the returned data to the database. But it looks like the application does not accept the data and return 422 : Unprocessable Entity
Here is the error message from the log file
W, [2016-02-29T12:21:54.865291 #22727] WARN -- : Can't verify CSRF token authenticity
I, [2016-02-29T12:21:54.865839 #22727] INFO -- : Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms)
F, [2016-02-29T12:21:54.870852 #22727] FATAL -- :
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
actionpack (4.2.4) lib/action_controller/metal/request_forgery_protection.rb:181:in `handle_unverified_request'
actionpack (4.2.4) lib/action_controller/metal/request_forgery_protection.rb:209:in `handle_unverified_request'
actionpack (4.2.4) lib/action_controller/metal/request_forgery_protection.rb:204:in `verify_authenticity_token'
I also tried disabling the CSRF
class MyController < ApplicationController
skip_before_action :verify_authenticity_token
end
But it still doesn't work.
Please help me get through this. I have been struggling with this issue for three days.
You can try this
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }

high_voltage gem not working for URLs not beginning with /pages

I can't seem to get thoughtbot's High Voltage gem to work for URLs that don't start with /pages. For URLs like www.example.com/pages/company/about, it works perfect. When I add this route:
match '/company/about' => 'high_voltage/pages#show', :id => 'about'
I get an internal server error:
Started GET "/company/about" for 127.0.0.1 at 2012-10-09 18:03:45 -0700
Processing by HighVoltage::PagesController#show as HTML
Parameters: {"id"=>"about"}
Completed 500 Internal Server Error in 2ms
ActionController::RoutingError (No such page: about):
high_voltage (1.2.0) app/controllers/high_voltage/pages_controller.rb:9:in `block in <class:PagesController>'
activesupport (3.2.8) lib/active_support/rescuable.rb:80:in `call'
activesupport (3.2.8) lib/active_support/rescuable.rb:80:in `rescue_with_handler'
actionpack (3.2.8) lib/action_controller/metal/rescue.rb:15:in `rescue_with_handler'
However, if I just put the about.html.erb page inside app/views/pages/about.html.erb, then everything works as expected with this route:
match '/:id' => 'high_voltage/pages#show'
or this route works too:
match '/about' => 'high_voltage/pages#show', :id => 'about'
jferris at GitHub informed me I have to make the id 'company/about' above. And that worked.

rails - InvalidAuthenticityToken for json/xml requests

For some reason I'm getting an InvalidAuthenticityToken when making post requests to my application when using json or xml. My understanding is that rails should require an authenticity token only for html or js requests, and thus I shouldn't be encountering this error. The only solution I've found thus far is disabling protect_from_forgery for any action I'd like to access through the API, but this isn't ideal for obvious reasons. Thoughts?
def create
respond_to do |format|
format.html
format.json{
render :json => Object.create(:user => #current_user, :foo => params[:foo], :bar => params[:bar])
}
format.xml{
render :xml => Object.create(:user => #current_user, :foo => params[:foo], :bar => params[:bar])
}
end
end
and this is what I get in the logs whenever I pass a request to the action:
Processing FooController#create to json (for 127.0.0.1 at 2009-08-07 11:52:33) [POST]
Parameters: {"foo"=>"1", "api_key"=>"44a895ca30e95a3206f961fcd56011d364dff78e", "bar"=>"202"}
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
thin (1.2.2) lib/thin/connection.rb:76:in `pre_process'
thin (1.2.2) lib/thin/connection.rb:74:in `catch'
thin (1.2.2) lib/thin/connection.rb:74:in `pre_process'
thin (1.2.2) lib/thin/connection.rb:57:in `process'
thin (1.2.2) lib/thin/connection.rb:42:in `receive_data'
eventmachine (0.12.8) lib/eventmachine.rb:242:in `run_machine'
eventmachine (0.12.8) lib/eventmachine.rb:242:in `run'
thin (1.2.2) lib/thin/backends/base.rb:57:in `start'
thin (1.2.2) lib/thin/server.rb:156:in `start'
thin (1.2.2) lib/thin/controllers/controller.rb:80:in `start'
thin (1.2.2) lib/thin/runner.rb:174:in `send'
thin (1.2.2) lib/thin/runner.rb:174:in `run_command'
thin (1.2.2) lib/thin/runner.rb:140:in `run!'
thin (1.2.2) bin/thin:6
/opt/local/bin/thin:19:in `load'
/opt/local/bin/thin:19
With protect_from_forgery enabled, Rails requires an authenticity token for any non-GET requests. Rails will automatically include the authenticity token in forms created with the form helpers or links created with the AJAX helpers--so in normal cases, you won't have to think about it.
If you're not using the built-in Rails form or AJAX helpers (maybe you're doing unobstrusive JS or using a JS MVC framework), you'll have to set the token yourself on the client side and send it along with your data when submitting a POST request. You'd put a line like this in the <head> of your layout:
<%= javascript_tag "window._token = '#{form_authenticity_token}'" %>
Then your AJAX function would post the token with your other data (example with jQuery):
$.post(url, {
id: theId,
authenticity_token: window._token
});
I had a similar situation and the problem was that I was not sending through the right content type headers - I was requesting text/json and I should have been requesting application/json.
I used curl the following to test my application (modify as necessary):
curl -H "Content-Type: application/json" -d '{"person": {"last_name": "Lambie","first_name": "Matthew"}}' -X POST http://localhost:3000/people.json -i
Or you can save the JSON to a local file and call curl like this:
curl -H "Content-Type: application/json" -v -d #person.json -X POST http://localhost:3000/people.json -i
When I changed the content type headers to the right application/json all my troubles went away and I no longer needed to disable forgery protection.
This is the same as #user1756254's answer but in Rails 5 you need to use a bit more different syntax:
protect_from_forgery unless: -> { request.format.json? }
Source: http://api.rubyonrails.org/v5.0/classes/ActionController/RequestForgeryProtection.html
Adding up to andymism's answer you can use this to apply the default inclusion of the TOKEN in every POST request:
$(document).ajaxSend(function(event, request, settings) {
if ( settings.type == 'POST' || settings.type == 'post') {
settings.data = (settings.data ? settings.data + "&" : "")
+ "authenticity_token=" + encodeURIComponent( window._token );
}
});
Since Rails 4.2, we have another way is to avoid verify_authenticity_token using skip_before_filter in your Rails App:
skip_before_action :verify_authenticity_token, only: [:action1, :action2]
This will let curl to do its job.
Ruby on Rails 4.2 Release Notes: https://guiarails.com.br/4_2_release_notes.html
As long as the JavaScript lives on the website served by Rails (for example: a JS snippet; or React app managed via webpacker) you can use the the value in csrf_meta_tags included in application.html.erb by default.
In application.html.erb:
<html>
<head>
...
<%= csrf_meta_tags %>
...
Therefore in the HTML of your website:
<html>
<head>
<meta name="csrf-token" content="XZY">
Grab the token from the content property and use it in the request:
const token = document.head.querySelector('meta[name="csrf-token"]').content
const response = await fetch("/entities/1", {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ authenticity_token: token, entity: { name: "new name" } })
});
This is similar to #andrewle's answer but there's no need for an additional token.
To add to Fernando's answer, if your controller responds to both json and html, you can use:
skip_before_filter :verify_authenticity_token, if: :json_request?

Resources