Rails Devise Ajax call - ruby-on-rails

I need to handle login with devise through an ajax call. Right now I have a devise login that is built with simple forms, what exists for devise is meant completely for the server side. the client side is built completely independently of ruby. The person on the client side needs to know how to send information through AJAX with the proper parameters to handle login and sign in.
Edit
My application.js no looks like this
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require jquery
//= require jquery_ujs
//= require_tree .
$(function(){
$.ajaxSetup({
beforeSend: function( xhr ) {
var token = $('meta[name="csrf-token"]').attr('content');
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
}
});
});
and this is my post request
function signIn(email, password){
var data = {user: {email: email, password: password}};
$.ajax({
url: 'http://localhost:3000/users/sign_in.json',
type: 'POST',
dataType: 'json',
data: data,
success: function(data, textStatus, xhr) {
alert('success');
alert(data); //to see what kind of outputs these have
alert(textStatus);
alert(xhr);
//called when successful
}
});
}
this gives this in my rails console
Started POST "/users/sign_in.json" for 127.0.0.1 at 2011-12-08 12:30:06 -0500
Processing by SessionsController#create as JSON
Parameters: {"user"=>{"email"=>"someone#hotmail.com", "password"=>"[FILTERED]"}}
WARNING: Can't verify CSRF token authenticity
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'someone#hotmail.com' LIMIT 1
(0.3ms) UPDATE "users" SET "last_sign_in_at" = '2011-12-08 17:29:23.030686', "current_sign_in_at" = '2011-12-08 17:30:07.069479', "sign_in_count" = 11, "updated_at" = '2011-12-08 17:30:07.069864' WHERE "users"."id" = 16
Completed 201 Created in 144ms (Views: 2.1ms | ActiveRecord: 0.0ms)
and in my browser when i inspect element and go to the console i get
XMLHttpRequest cannot load http://localhost:3000/users/sign_in.json. Origin null is not allowed by Access-Control-Allow-Origin.
And none of my alerts show.
What do i need to change

By default, You should write some code like this:
First to setup CSRF Token in your application.js:
$(function(){
$.ajaxSetup({
beforeSend: function( xhr ) {
var token = $('meta[name="csrf-token"]').attr('content');
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
}
});
});
Second to post your login information:
$.ajax({
url: '/users/sign_in.json',
type: 'POST',
dataType: 'json',
data: {user: {email: 'email#example.com', password: 'password'}},
success: function(data, textStatus, xhr) {
//called when successful
}
});

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.

How to succesfully save a json object recieved as POST- ruby on rails

I'm trying to create an app that allows user to submit reviews.
I used survey.js which returns a json object with the responses, here is the coffee script file which sends the request to the desired url:
$.ajax({
type:'POST',
url: "/surveys/save",
data: survey.data,
success: alert("saved"),
dataType: JSON
And in my Controller I try to save the appropriate parameters:
def create
if validate_user
#submission = Submission.new(submission_params)
if #submission.save
redirect_to '/surveys/saved'
else
redirect_to '/surveys/nosaved'
end
end
end
However I see this output from the server:
"Started POST "/surveys/save" for ::1 at 2016-08-01 00:21:47 -0400
Processing by SurveysController#create as */*
Parameters: {"question1"=>"eh", "question2"=>"1", "question3"=>"3", "question4"=>"1", "question5"=>"1", "question6"=>"3", "question7"=>"4", "question8"=>"1", "question9"=>"2", "question10"=>"1"}
Can't verify CSRF token authenticity"
Please advise, from my research it seems that I need to prepend the authenticity token to my request but i'm not sure how to go about that.
Thanks!
****UPDATE**
I do have the CSRF meta tag in my application layout file
******UPDATE 2*******
I tried the following in the Coffee script, it resulted in the same output.
$.ajax({
type:'POST',
beforeSend: test = (xhr)-> return xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf token"]').attr('content'))
url: "/surveys/save",
data: survey.data,
success: alert("saved"),
dataType: JSON
});
Below steps will do the trick for you.
Make sure you have the csrf_meta tag in your application layout file.
Add beforeSend to all ajax requet to set header like below.
$.ajax({
type: 'POST',
beforeSend: function(xhr) {
return xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf token"]').attr('content'));
},
url: "/surveys/save",
data: survey.data,
dataType: JSON,
success: alert("saved")
});
The solution was as Abid attempted to do, but by just declaring the header as such in the coffee script.
$.ajax({
type:'POST',
headers: {'X-CSRF-Token': $('meta[name="csrf token"]').attr('content')},
url: "/surveys/save",
data: survey.data,
success: alert("saved"),
dataType: JSON
})

Ruby On Rails authentication issue with http basic(the authentication process is triggered twice for every request)

This is the request I send to the rails controller:
function login(){
$.ajax({
type: 'GET',
url: '/api/myapi/show',
username: 'username',
password: 'password',
contentType: "application/json; charset=utf-8",
success: function(data){
console.log(data);
},
error: function(xhr, ajaxOptions, thrownError) {
console.log(arguments);
}
});
}
login function is used as follows:
<body onload='login();'>
This is the controller:
class Api::MyApi::BaseController < ApplicationController
before_filter :authenticate
attr_reader :user
def authenticate
authenticate_or_request_with_http_basic do |username, password|
#authenticate method checks if user with username and password exists in database
#user = User.authenticate(username, password)
end
end
end
When I send the request, this is what's printed in the terminal:
Started GET "/api/myapi/show" for 127.0.0.1 at 2015-12-15 09:42:22 +0100
Processing by Api::MyApi#show as JSON
Parameters: {"id"=>"show", "test"=>{}}
Filter chain halted as :authenticate rendered or redirected
Completed 401 Unauthorized in 0ms (ActiveRecord: 0.0ms)
Started GET "/api/myapi/show" for 127.0.0.1 at 2015-12-15 09:42:22 +0100
Processing by Api::MyApi#show as JSON
Parameters: {"id"=>"show", "test"=>{}}
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`authorized` = 1 AND `users`.`verification_approved` = 1 AND `users`.`login` = 'user_login' LIMIT 1
Location Load (0.1ms) SELECT `locations`.* FROM `locations` WHERE `locations`.`id` = 9999 LIMIT 1
Rendered api/myapi/show.json.rabl (0.5ms)
Completed 200 OK in 8ms (Views: 2.6ms | ActiveRecord: 0.7ms)
As you can see, it tries to authenticate twice and fails the first time. It doesn't even get inside "authenticate_or_request_with_http_basic" because if I write a print statement inside the "authenticate_or_request_with_http_basic", it doesn't get printed the first time(when authentication fails), but does get printed the second time.
Things I tried:
1) When Removing the before_filter completely and just authenticating in the show method, the issue doesn't occur anymore.
2) When keeping/using the before_filter but replacing the authenticate_or_request_with_http_basic with 'true' like this:
def authenticate
true
end
the issue doesn't occur either.
3) The issue doesn't occur when I send a request with python:
import requests
r = requests.get('URL_TO__RoR_Controller', auth=('username', 'password'))
print r.text
UPDATE:
This might be useful info: The request is sent every 10 seconds, and the credentials are sent with every request. Perhaps this has something to do with the issue.
You are sending username and password as params
AFAIK Basic auth works by setting the authorization headers
Use jQuery's beforeSend callback to add an HTTP header with the authentication information: http://api.jquery.com/jQuery.ajax/
beforeSend: function (xhr) {
xhr.setRequestHeader ("Authorization", "Basic " + btoa(username + ":" + password));
},
The btoa() method encodes a string in base-64.
In your controller you can check the headers with
request.env["HTTP_AUTHORIZATION"]
Let me know if this works for you.

Routing error: No route matches [OPTIONS]

When I try to set my request header token, I receive an error:
ActionController::RoutingError (No route matches [OPTIONS] "/data"):
Here is the ajax call:
$.ajax({
url: this.hostName + url,
type: 'POST',
data: data,
dataType: 'json',
beforeSend: function( xhr ) {
xhr.setRequestHeader( 'X-CSRF-Token', $( 'meta[name="csrf-token"]' ).attr( 'content' ) );
},
success: function(response) {
console.log('success');
console.log(response);
},
error: function(response) {
console.log('error');
console.log(response);
}
});
If I leave the request header out:
Started POST "/data" for 127.0.0.1 at 2012-07-24 18:37:22 -0700
but I get a warning stating:
WARNING: Can't verify CSRF token authenticity
Any ideas as to why this is happening?
Have you tried
$.ajax({
url: this.hostName + url,
type: 'POST',
headers: { 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') }
data: { param : 'something' },
dataType: 'json'
});
And then in your controller params[:param] will give you 'something'.
If you are interested to know why you need all that X-CSRF-Token stuff, this is a good read. Basically, Rails automatically includes that token in your page to keep your application safe. If you inspect any page of your site, you'll see in the head of your HTML document something like this:
<meta content="guma1QdmO9Tn9SB4yV4DonkY4xf4Sy6lIvrFyHIaR1U=" name="csrf-token">
This is created by the line <%= csrf_meta_tags %> included in your application.html.erb file.
Rails automatically includes this token in regular non-GET requests to keep them safe. But with javascript and ajax, you have to do this manually by searching the DOM for the token with the jQuery function $('meta[name="csrf-token"]'.
Now, this isn't very efficient because you are searching for that token every time you are making a request. So what you should do, is use ajaxSetup, like this:
$.ajaxSetup({
type: 'POST',
headers: { 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') }
dataType: 'json'
});
And now, whenever you want to send a request, all you need is this:
$.ajax({
url: this.hostName + url,
data: { param : 'something' }
});
EDIT: Ah, I see. What do you get when you do alert(this.hostName + url)? And how are you declaring your routes? Because if I remember correctly, you can use relative urls instead of absolute urls, you don't need the root part. Here's an example:
# routes.rb
post "relative_url" => 'controller#action'
# file.js
$.ajax({
url: "relative_url",
data: { param : 'something' }
});
You should try giving relative url instead of absolute in ajax. I had the same problem, and worked by changing url

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.

Resources