Ajax POST can't seem to authenticate with Devise - ruby-on-rails

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.

Related

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.

Respond with script from a Rails API app with limited middleware (config.api_only = true)?

I have a Rails 5 app build as an api app. So by default it only responds with json.
But for one specific request I need the app to respond with a script.
In a regular Rails app I would simply put my script in a js.erb file. This won't work here.
If my controller action looks like this:
def respond_with_js
end
and I request the app like this:
$.getScript("https://myapp.example.com/respond_with_js");
it responds with 204 No Content:
Started GET "/respond_with_js" for 127.0.0.1 at 2018-06-27 20:28:44 +0200
Processing by ApplicationController#respond_with_js as */*
Completed 204 No Content in 0ms
How do I work around this?
You cannot request as script, if rails server is only api version.
Rails by default responds json.
def respond_with_json
render json: {message: "works"}, status: :ok
end
To request it, you need to request as json dataType:
$.ajax({
url: "https://myapp.example.com/respond_with_json",
method: "GET",
dataType: "json",
success: function(res){
console.log(res.message)
}
})

Rails Request Content-Type Seems incorrect when accessing API with AngularJS

I have a straightforward Rails 4.1.4 application and I'm trying to connect an AngularJS application on a separate host to it's API. Whilst I'm have no problem accessing it, Rails seems to think the request is HTML and ignores the Content-Type: 'application/json'
Started GET "/api/v1/locations?access_token=xxx&headers=%7B%22Content-type%22:%22application%2Fjson%22%7D" for 127.0.0.1 at 2014-09-03 17:12:11 +0100
Processing by Api::V1::LocationsController#index as HTML
Parameters: {"access_token"=>"xxx", "headers"=>"{\"Content-type\":\"application/json\"}"}
And in my NG application, I've tried a number of combinations of headers including:
app.factory('Location', ['$resource', "$localStorage",
function($resource, $localStorage){
return $resource('http://my-api.com/api/v1/locations/', {headers:{'Content-type' : 'application/json'}}, {
query: {
method: 'GET',
headers: {'Content-type': 'application/json'},
isArray: true,
dataType: 'json',
params: { access_token: $localStorage.accessToken }
}...
The response looks ok on the NG side, it's responding with JSON despite only having this in my locations controller:
class Api::V1::LocationsController < Api::V1::BaseController
doorkeeper_for :all
before_filter :authorize
respond_to :json
def index
#locations = #current_user.locations.order("created_at desc").limit(5)
respond_with #locations
end
end
I have also set (and tested unset) the cors headers.
I read somewhere that Rails won't read the content-type header if there's forward slashes in it...
Whilst this doesn't appear to be causing many issues, I do think it's interfering with Doorkeeper that's part of the application.
This wasn't a Rails problem. Turns out I needed to fiddle with some headers etc. in the NG config.
I added the following to app.js
$httpProvider.defaults.useXDomain = true;
// $httpProvider.defaults.withCredentials = true;
delete $httpProvider.defaults.headers.common["X-Requested-With"];
$httpProvider.defaults.headers.common["Accept"] = "application/json";
$httpProvider.defaults.headers.common["Content-Type"] = "application/json";
The second line threw an error but I've left in there for good measure.

WARNING: Can't verify CSRF token authenticity rails

I am sending data from view to controller with AJAXand I got this error:
WARNING: Can't verify CSRF token authenticity
I think I have to send this token with data.
Does anyone know how can I do this ?
Edit: My solution
I did this by putting the following code inside the AJAX post:
headers: {
'X-Transaction': 'POST Example',
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
You should do this:
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')
}
});
The best way to do this is actually just use <%= form_authenticity_token.to_s %> to print out the token directly in your rails code. You dont need to use javascript to search the dom for the csrf token as other posts mention. just add the headers option as below;
$.ajax({
type: 'post',
data: $(this).sortable('serialize'),
headers: {
'X-CSRF-Token': '<%= form_authenticity_token.to_s %>'
},
complete: function(request){},
url: "<%= sort_widget_images_path(#widget) %>"
})
If I remember correctly, you have to add the following code to your form, to get rid of this problem:
<%= token_tag(nil) %>
Don't forget the parameter.
Indeed simplest way. Don't bother with changing the headers.
Make sure you have:
<%= csrf_meta_tag %> in your layouts/application.html.erb
Just do a hidden input field like so:
<input name="authenticity_token"
type="hidden"
value="<%= form_authenticity_token %>"/>
Or if you want a jQuery ajax post:
$.ajax({
type: 'POST',
url: "<%= someregistration_path %>",
data: { "firstname": "text_data_1", "last_name": "text_data2", "authenticity_token": "<%= form_authenticity_token %>" },
error: function( xhr ){
alert("ERROR ON SUBMIT");
},
success: function( data ){
//data response can contain what we want here...
console.log("SUCCESS, data="+data);
}
});
Ugrading from an older app to rails 3.1, including the csrf meta tag is still not solving it. On the rubyonrails.org blog, they give some upgrade tips, and specifically this line of jquery which should go in the head section of your layout:
$(document).ajaxSend(function(e, xhr, options) {
var token = $("meta[name='csrf-token']").attr("content");
xhr.setRequestHeader("X-CSRF-Token", token);
});
taken from this blog post: http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails.
In my case, the session was being reset upon each ajax request. Adding the above code solved that issue.
Make sure that you have <%= csrf_meta_tag %> in your layout
Add a beforeSend to include the csrf-token in the ajax request to set the header. This is only required for post requests.
The code to read the csrf-token is available in the rails/jquery-ujs, so imho it is easiest to just use that, as follows:
$.ajax({
url: url,
method: 'post',
beforeSend: $.rails.CSRFProtection,
data: {
// ...
}
})
The top voted answers here are correct but will not work if you are performing cross-domain requests because the session will not be available unless you explicitly tell jQuery to pass the session cookie. Here's how to do that:
$.ajax({
url: url,
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
},
xhrFields: {
withCredentials: true
}
});
I just thought I'd link this here as the article has most of the answer you're looking for and it's also very interesting
http://www.kalzumeus.com/2011/11/17/i-saw-an-extremely-subtle-bug-today-and-i-just-have-to-tell-someone/
You can write it globally like below.
Normal JS:
$(function(){
$('#loader').hide()
$(document).ajaxStart(function() {
$('#loader').show();
})
$(document).ajaxError(function() {
alert("Something went wrong...")
$('#loader').hide();
})
$(document).ajaxStop(function() {
$('#loader').hide();
});
$.ajaxSetup({
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))}
});
});
Coffee Script:
$('#loader').hide()
$(document).ajaxStart ->
$('#loader').show()
$(document).ajaxError ->
alert("Something went wrong...")
$('#loader').hide()
$(document).ajaxStop ->
$('#loader').hide()
$.ajaxSetup {
beforeSend: (xhr) ->
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
}
If you are not using jQuery and using something like fetch API for requests you can use the following to get the csrf-token:
document.querySelector('meta[name="csrf-token"]').getAttribute('content')
fetch('/users', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')},
credentials: 'same-origin',
body: JSON.stringify( { id: 1, name: 'some user' } )
})
.then(function(data) {
console.log('request succeeded with JSON response', data)
}).catch(function(error) {
console.log('request failed', error)
})
oops..
I missed the following line in my application.js
//= require jquery_ujs
I replaced it and its working..
======= UPDATED =========
After 5 years, I am back with Same error, now I have brand new Rails 5.1.6, and I found this post again. Just like circle of life.
Now what was the issue is:
Rails 5.1 removed support for jquery and jquery_ujs by default, and added
//= require rails-ujs in application.js
It does the following things:
force confirmation dialogs for various actions;
make non-GET requests from hyperlinks;
make forms or hyperlinks submit data asynchronously with Ajax;
have submit buttons become automatically disabled on form submit to prevent double-clicking.
(from: https://github.com/rails/rails-ujs/tree/master)
But why is it not including the csrf token for ajax request? If anyone know about this in detail just comment me. I appreciate that.
Anyway I added the following in my custom js file to make it work (Thanks for other answers to help me reach this code):
$( document ).ready(function() {
$.ajaxSetup({
headers: {
'X-CSRF-Token': Rails.csrfToken()
}
});
----
----
});
Use jquery.csrf (https://github.com/swordray/jquery.csrf).
Rails 5.1 or later
$ yarn add jquery.csrf
//= require jquery.csrf
Rails 5.0 or before
source 'https://rails-assets.org' do
gem 'rails-assets-jquery.csrf'
end
//= require jquery.csrf
Source code
(function($) {
$(document).ajaxSend(function(e, xhr, options) {
var token = $('meta[name="csrf-token"]').attr('content');
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
});
})(jQuery);
If you're using javascript with jQuery to generate the token in your form, this works:
<input name="authenticity_token"
type="hidden"
value="<%= $('meta[name=csrf-token]').attr('content') %>" />
Obviously, you need to have the <%= csrf_meta_tag %> in your Ruby layout.
I struggled with this issue for days. Any GET call was working correctly, but all PUTs would generate a "Can't verify CSRF token authenticity" error. My website was working fine until I had added a SSL cert to nginx.
I finally stumbled on this missing line in my nginx settings:
location #puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Forwarded-Proto https; # Needed to avoid 'WARNING: Can't verify CSRF token authenticity'
proxy_pass http://puma;
}
After adding the missing line "proxy_set_header X-Forwarded-Proto https;", all my CSRF token errors quit.
Hopefully this helps someone else who also is beating their head against a wall. haha
For those of you that do need a non jQuery answer you can simple add the following:
xmlhttp.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
A very simple example can be sen here:
xmlhttp.open("POST","example.html",true);
xmlhttp.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
xmlhttp.send();
if someone needs help related with Uploadify and Rails 3.2 (like me when I googled this post), this sample app may be helpful:
https://github.com/n0ne/Uploadify-Carrierwave-Rails-3.2.3/blob/master/app/views/pictures/index.html.erb
also check the controller solution in this app
I'm using Rails 4.2.4 and couldn't work out why I was getting:
Can't verify CSRF token authenticity
I have in the layout:
<%= csrf_meta_tags %>
In the controller:
protect_from_forgery with: :exception
Invoking tcpdump -A -s 999 -i lo port 3000 was showing the header being set ( despite not needing to set the headers with ajaxSetup - it was done already):
X-CSRF-Token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
DNT: 1
Content-Length: 125
authenticity_token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
In the end it was failing because I had cookies switched off. CSRF doesn't work without cookies being enabled, so this is another possible cause if you're seeing this error.

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