Form remote: true authenticity_token disappear - ruby-on-rails

If I set remote: true to my form authenticity_token disappear. I have to add authenticity_token: true to my form? What about caching? If I cache the form I have to add authenticity_token: true? Is a problem that authenticity_token is cached? Thank you

Are you using the jquery-rails gem in your app? This'll add the CSRF token to remote: true AJAX form submissions automagically:
https://github.com/rails/jquery-rails/blob/master/vendor/assets/javascripts/jquery_ujs.js#L69
// Make sure that every Ajax request sends the CSRF token
CSRFProtection: function(xhr) {
var token = rails.csrfToken();
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
}

Related

Cannot figure out why I am getting random 'Can't verify CSRF token authenticity' errors on my rails controllers

The Problem
I have a multipage react application & rails backend that I am using as an API.
After a user logs out of the app, rails will throw CSRF errors when receiving any subsequent POST or DELETE requests until I perform a full page refresh in my browser.
e.g. After logging out, the login form POST request will cause 422 errors until I refresh the page. Then when logged in, post login POST / DELETE requests will randomly also trigger 422 errors until I refresh the page
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms | Allocations: 775)
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
actionpack (6.0.3.4) lib/action_controller/metal/request_forgery_protection.rb:215:in `handle_unverified_request'
actionpack (6.0.3.4) lib/action_controller/metal/request_forgery_protection.rb:247:in `handle_unverified_request'
The CSRF token is being set in the application.html.erb file
<%= csrf_meta_tags %>
I have created an axios component to parse this token
const token = document.querySelector('[name="csrf-token"]') || {content: 'no-csrf-token'}
const axiosPost = axios.create({
headers: {
common: {
'X-CSRF-Token': token.content
}
}
})
export default axiosPost
And I am importing this in various other components & using it to make the requests e.g.
import AuthContext from './AuthContext'
const Logout = () => {
const {authState, setAuthState} = useContext(AuthContext)
const handleLogout = () => {
axiosPost.delete('/users/sign_out', {}, { withCredentials: true })
.then((resp) => {
setAuthState(false)
})
When the setAuthState value is set to false, my App.js component will re-render the page & display the Login component only.
Interesting, if I replace this logout / state driven re-render with
axiosPost.delete('/users/sign_out', {}, { withCredentials: true })
.then((resp) => {
setAuthState(false)
window.location.href = '/login'
})
It triggers a page refresh and I don't get the CSRF error on the backend (at least not as frequently).
I am starting to think that the CSRF token in the layout needs to be reloaded / refreshed after a user logs out of the application but maybe this is just another red herring.
One other thing, I did overwrite the destroy method in the devise sessions controller with
def destroy
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
if signed_out
head :no_content
end
end
I don't see how this method would cause the CSRF issue, but adding it for additional context.
Application Stack
Rails 6.0.3.4 on the backend (full stack, not just api)
CSRF protection enabled
Devise for user authentication & to authorize access to protected
controllers
React on the front-end.
My react app is initialized once through application.html.erb
Any help / guidance is appreciated

CSRF token error occurs when using turbolinks with ruby on rails

A CSRF token error (Can't verify CSRF token authenticity.) will occur if Post transmission is performed on the transition page using turbolinks.
But, when reloading the page, no error occurs.
How can I solve it?
Mr.Mark
Thank you for answering.
Is it due to the fact that the csrf-token of the header is different from the csrf-token of the form?
Why is csrf-token different when using turbolink?
I solved it in the following way, but what do you think?
$(document).on('turbolinks:load', function() {
token = $("meta[name='csrf-token']").attr("content");
$("input[name='authenticity_token']").val(token)
});
I know this is a year old question but this might help someone in need.
The problem here is the csrf-token in meta tag is different from
authenticity_token in the form. I believe turbolinks could be the culprit here.
The solution that worked for me is:
$(document).on('turbolinks:load', function(){ $.rails.refreshCSRFTokens(); });
You will need rails-ujs or jquery-ujs included with your app. If you're on Rails 6, I believe you already have rails-ujs by default.
This is a popular question the last few days...
I'd suggest following:
WARNING: Can't verify CSRF token authenticity rails
Make sure that you have <%= csrf_meta_tag %> in your layout
Add beforeSend to all the ajax request to set the header like below:
$.ajax({ url: 'YOUR URL HERE',
type: 'POST',
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: 'someData=' + someData,
success: function(response) {
$('#someDiv').html(response);
}
});
To send token in all requests you can use:
$.ajaxSetup({
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
});
👋🏻 Rails is expecting the form authenticity_token but the CSRF token from the meta tag is being sent instead because you are POSTing a form with any of this options on the Rails form helper:
turbo: false
local: true
remote: false
I solved it by copying the authenticity token from the meta tag, to the form before submitting the form:
// application.js
function copyCSRFMetaTagToFormAuthenticityToken() {
document.querySelector('input[name="authenticity_token"]').value = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
}
window.copyCSRFMetaTagToFormAuthenticityToken = copyCSRFMetaTagToFormAuthenticityToken
Then in my template:
<%= form_with(url: save_custom_connection_path, method: :post, data: { turbo: false }, :html => { :onsubmit => "window.copyCSRFMetaTagToFormAuthenticityToken()" }) do |form| %>
Notice:
:onsubmit => "window.copyCSRFMetaTagToFormAuthenticityToken()"
I believe this does not represent any security risk because it's using the view's CSRF token anyway.

Can't verify CSRF token authenticity Rails/React

I have a react component in my rails app where I'm trying to use fetch() to send a POST to my rails app hosted on localhost, this gives me the error:
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
I'm using devise gem to handle user/registrations and logins.
I have tried to remove protect_from_forgery with: :exception
Here is the code for my fetch,
this.state.ids.sub_id});
fetch(POST_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: body
}).then(res => res.json()).then(console.log);
How can I get the csrf token and send it through the form so that it will pass?
Ideally I would like to just send it through the headers but I have no idea how to access the token.
The simplest way to do this, if you are merely embedding a react component in your Rails view, is to retrieve the csrf token from your rails view and then pass it as a header in your fetch api call.
You can get the csrf token by doing something like this:
const csrf = document.querySelector("meta[name='csrf-token']").getAttribute("content");
And then you just pass it as a header in your fetch call:
...
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf
},
...
I normally don't use fetch, so not too sure about the exact syntax, but this should help guide you.
Thanks! I ended up with this as a working solution:
In the view that renders my react component
<% csrf_token = form_authenticity_token %>
<%= react_component('ExerciseDisplay', {
program: #program.subprograms.first.exercises, ids: {sub_id: #program.subprograms.first.id, team_id: #team.id, token: csrf_token}
}) %>
I passed the token into state, then accessed it via fetch:
fetch(POST_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': this.state.ids.token
},
body: body
}).then(res => res.json()).then(console.log);
Inside your form element, add a authenticity_token:
<input type="hidden" name="authenticity_token" value={csrf}/>
Set the value as per the below:
const csrf = document.querySelector("meta[name='csrf-token']").getAttribute("content");
I too faced the same issue when using rails 5.2 and i was able to fix this issue by adding
protect_from_forgery with: :null_session in application_controller.rb i got this from here,please refer this

How can I have a Rails API set a JWT in a cookie on a client that is running on another domain?

Long time lurker, first time poster here.
There are many good guides and resources about JWTs and how and where to store them. But I'm running into an impasse when it comes to securely storing and sending a JWT between a ReactJS/Flux app running on a Node server and a completely separate Rails API.
It seems most guides tell you to just store the JWT in local storage and pluck it out for every AJAX request you make and pass it along in a header. https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/ warns against this, however, since local storage is not secure and a malicious person could access that token. It recommends storing it in the cookie instead and just letting the web browser pass it along with each request.
That sounds fine to me since from what I understand cookies get conveniently sent along with every request anyway. It means I can just make AJAX requests from my ReactJS app to my Rails API and have the API pluck it out, check it, and do it's thing.*
The problem I'm running into is my Node application doesn't set a cookie from the response it gets back from the Rails API even though the Rails API (running on localhost:3000) returns a Set-Cookie header and sends it back to the ReactJS/Node app (running on localhost:8080).
Here's my login controller action on my Rails API side:
class V1::SessionsController < ApplicationController
def create
user = User.where(email: params[:user][:email]).first!
if user && user.authenticate(params[:user][:password])
token = issue_new_token_for(user)
# I've tried this too.
# cookies[:access_token] = {
# :value => token,
# :expires => 3.days.from_now,
# :domain => 'https://localhost:8080'
# }
response.headers['Set-Cookie'] = "access_token=#{token}"
render json: { user: { id: user.id, email: user.email }, token: token }, status: 200
else
render json: { errors: 'username or password did not match' }, status: 422
end
end
end
The gist of it is it takes an email and password, looks the user up, and generates JWT if the info checks out.
Here's the AJAX request that is calling it from my Node app:
$.ajax({
url: 'http://localhost:3000/v1/login',
method: 'post',
dataType: 'json',
data: {
user: {
email: data.email,
password: data.password
},
callback: '' //required to get around ajax CORS
},
success: function(response){
console.log(response);
},
error: function(response) {
console.log(response);
}
})
Inspecting the response from the Rails API shows it has a Set-Cookie header with a value of access_token=jwt.token.here
Screenshot:
Chrome Dev Tools Inspector Screenshot
However, localhost:8080 does not show any cookies set and subsequent AJAX calls from my Node/React app do not have any cookies being sent along with them.
My question is, what piece(s) am I misunderstanding. What would I have to do to make storing JWTs in cookies work in this scenario?
A follow-up question: assuming storing the JWT in a cookie is not an option, what potential security risks could there be with storing the JWT in local storage (assuming I don't put any sensitive info in the JWT and they all expire in some arbitrary amount of time)?
*this may be a fundamental misunderstanding I have. Please set me straight if I have this wrong.
Side-notes that may be of interest:
My Rails API has CORS setup to only allow traffic from localhost:8080
in development.
In production, the Node/React app will probably be
running on a main domain (example.com) and the Rails API will be
running on a sub domain (api.example.com), but I haven't gotten that
far yet.
There's nothing sensitive in my JWT, so local storage is an
option, but I want to know why my setup doesn't work with cookies.
Update elithrar submitted an answer that worked:
I needed to modify my AJAX request with xhrFields and crossDomain as well as tell jQuery to support cors:
$.support.cors = true;
$.ajax({
url: 'http://localhost:3000/v1/login',
method: 'post',
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
data: {
user: {
email: data.email,
password: data.password
}
},
success: function(response){
console.log(response);
},
error: function(response) {
console.log(response);
}
})
And I added credentials: true and expose: true to my Rack Cors configuration on my Rails API (the * is only for my development environment):
config.middleware.insert_before 0, 'Rack::Cors' do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :path, :options], credentials: true, expose: true
end
end

Ajax POST can't seem to authenticate with Devise

Devise refuses to authenticate me when sending an ajax POST request.
What have I missed?
rails (4.1.6)
devise (3.4.1)
jquery-rails (2.3.0)
jquery-ui-rails (5.0.3)
Controller
class SeenEpisodesController < ApplicationController
before_filter :authenticate_user! #Devise
def mark_episode
#do stuff
respond_to do |format|
format.js
end
end
end
Javascript
Trying both with setting a header and a payload.
$.ajax({
type: "POST",
url: url,
dataType: "script",
data: {
authenticity_token: encodeURIComponent($('meta[name="csrf-token"]').attr('content'))
},
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
}
});
I have also tried without setting the tokens myself. jquery_ujs sets the header for me!
Yes I have <%= csrf_meta_tags %> in my layout.
Javascript manifest
//= require jquery
//= require jquery_ujs #Have tried with or without
Rails console output
Processing by SeenEpisodesController#mark_episode as JS
Parameters: {"mark"=>"1", "episode_id"=>"887795", "season_id"=>"65577"}
Completed 401 Unauthorized in 9ms
Started GET "/account/sign_in.js" for 192.168.0.10 at 2015-02-28 19:44:18 +0100
Processing by Devise::SessionsController#new as JS
Dev tools network output
%253D - try to decode this part of your authenticity_token. It will equal to =. So, seems like you have encoded authenticity_token incorrectly (twice).
You don't need to do encodeURIComponent when posting with jQuery. jQuery encodes the data for you anyway, which is leading to your data being encoded twice which will result in invalid authenticity token being submitted.

Resources