Phoenix - invald CSRF on AJAX post - ajax

I'm learning to use the Phoenix framework, and I'm trying to do an AJAX post to a controller action - however, I'm running into a problem with the CSRF protection.
For starters, I'm not using a form - just want to pass text from an input to the controller:
<input type="text" id="raw-input" />
<button id="send-button">Send it!</button>
<script>
$("#send-button").click(function(){
var input = $("#raw-input").val();
$.ajax({
url: "/test/process",
type: "POST",
dataType: "json",
beforeSend: function(xhr) {xhr.setRequestHeader("X-CSRF-Token", $("meta[name='csrf-token']").attr("content"))},
data: {"input" : input},
success: function(response){
console.log(response);
}
});
});
</script>
The controller (not worried about doing anything input yet... just want to verify a successful post!):
def process(conn, %{"input" => input}) do
IO.puts "got it!"
end
And the router:
post "/test/process", TestController, :process
I pretty much lifted the $.ajax call from a Rails app where it was working fine, but it's not doing the trick here - running this returns a 403 error and logs (Plug.CSRFProtection.InvalidCSRFTokenError) invalid CSRF (Cross Site Request Forgery) token, make sure all requests include a valid '_csrf_token' param or 'x-csrf-token' header.
Can anyone offer any guidance? Thank you!

This is because Phoenix does not create a meta tag with the CSRF token by default. They're only included in forms generated by Phoenix's helper functions, and they're in a hidden input.
To get a CSRF token programatically in Phoenix, you can call Plug.CSRFProtection.get_csrf_token/0. There are many ways to pass this to your JS. You can add a meta tag to your layout to include it in every page but that might not be very efficient since it'll be generated for all pages. You can also just store it in a JS variable in the views that you require them in:
<input type="text" id="raw-input" />
<button id="send-button">Send it!</button>
<script>
$("#send-button").click(function(){
var CSRF_TOKEN = <%= raw Poison.encode!(Plug.CSRFProtection.get_csrf_token()) %>;
var input = $("#raw-input").val();
$.ajax({
url: "/test/process",
type: "POST",
dataType: "json",
beforeSend: function(xhr) {
xhr.setRequestHeader("X-CSRF-Token", CSRF_TOKEN);
},
data: {"input" : input},
success: function(response){
console.log(response);
}
});
});
</script>

Phoenix already has helper csrf_meta_tag. Include it in the layout like so:
<html lang="en">
<head>
<%= csrf_meta_tag %>
...
And then use it in your js like so: $("meta[name='csrf-token']").attr("content")

If you want to skip the CSRF token check (in case you are developing APIs only) then you can comment out below line -
plug :protect_from_forgery
inside your respective _web/router.ex

Related

Ajax link does not send POST request

I have the following ajax link:
#Html.AjaxActionLink(item.Name, "https://test.testspace.space/storage/Data/stream?tokenValue=e58367c8-ec11-4c19-995a-f37ad236e0d2&fileId=2693&position=0", new AjaxOptions { HttpMethod = "POST" })
However, although it is set to POST, it seems that it still sends GET request.
UPDATE:
As suggested below, I also tried with js functuion like this:
function DownloadAsset() {
alert("downloading");
$.ajax({
type: "POST",
url: 'https://test.testspace.space/storage/Data/stream?tokenValue=add899c5-7851-4416-9b06-4587528a72db&fileId=2693&position=0',
success: function () {
}
});
}
However, it still seems to be GET request. Parameters must be passed as query and not in the body of the request because they are expected like that by the target action. I don't know why (it would be more natural to have GET request) but back-end developer designed it like this due to some security reason.
If I use razor form like this, then it works:
<html>
<form action="https://test.testspace.space/storage/Data/stream?tokenValue=2ec3d6d8-bb77-4c16-bb81-eab324e0d29a&fileId=2693&position=0" method="POST">
<div>
<button>Send my greetings</button>
</div>
</form>
</html>
However, I can not use this because I already have bigger outer form on the page and I'll end up with nested forms which is not allowed by razor/asp.
The only way is to use javascript but for some reason it does not make POST request.
#Html.AjaxActionLink will generate to <a> tag,and tag will only have HttpGet request method.If you want to send HttpPost with <a> tag,you can use it call a function with ajax,here is a demo:
link
<script>
function myFunction() {
$.ajax({
type: "POST",
url: "https://test.testspace.space/storage/Data/stream",
data: { tokenValue: "e58367c8-ec11-4c19-995a-f37ad236e0d2", fileId: "2693", position:0 },
success: function (data) {
}
});
</script>
Since you want to make a POST request, but the values need to be as query string params in the URL, you need to use jquery.Param.
see https://api.jquery.com/jquery.param/.
You should set the params, like below :
$.ajax({
url: 'your url',
type: 'POST',
data: jQuery.param({ tokenValue: "your token", fileId : "2693", position: 0}) ,
...
Try this instead,
First remove the action url from the from
Second put the result in the success function to return response
and for parameters, I always use FormData() interface to post with Ajax
And last don't forget to include dataType, contentType, processData to not get an unexpected behavior
your code will look like this
var form_data = new FormData();
form_data.append('tokenValue' ,'add899c5-7851-4416-9b06-4587528a72db&fileId=2693');
form_data.append('position' ,'position');
$.ajax({
type: "POST",
dataType: 'json',
contentType:false,
processData:false,
data: form_data,
url: 'https://test.testspace.space/storage/Data/stream',
success: function (result) {
}
});

Ajax call with flask-seasurf

I am developing a small app on localhost and using flask-seasurf to prevent csrf attacks. All my non-ajax forms work correctly with flask-seasurf. I have one form that triggers an ajax call to '/checkajax' on form submit; this worked until I started to use flask-seasurf but now I get a console error and the ajax doesn't work:
Warning in flask_seasurf: Forbidden (CSRF token missing or incorrect): /checkajax
The form triggering the ajax call has the standard hidden field containing the 'csrf_token()' function call of flask-seasurf embedded in the jinja page template:
<input id="csrf-token" type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
The ajax call structure is:
$("#submit").submit(function(event) {
event.preventDefault();
$.ajax({
url: "/checkajax",
data: {...},
type: "POST",
datatype: "text",
success: function(response){
...
},
error: function(response) {
...
}
});
});
I can see from the site cookie that there is an entry for '_csrf_token' generated by flask-seasurf. Can anyone give some insight as to why this ajax call is now not working?
The solution to my problem was to modify the header of the ajax call to include X-CSRFToken defined as the flask-seasurf token from my form:
var csrf_token = $("csrf-token").val()
$("#submit").submit(function(event) {
event.preventDefault();
$.ajax({
headers: {"X-CSRFToken", csrf_token},
url: "/checkajax",
data: {...},
type: "POST",
datatype: "text",
success: function(response){
...
},
error: function(response) {
...
}
});
});
Hope that helps someone else.

Converting formData to forms.Form in django

I would like to allow user to key in a quiz code and gets an alert to tell whether if the code is still invalid without refreshing the page. I already read a lot of Django AJAX and JQuery tutorials but most of them seem outdated because they do not cover the part where csrf token must be send.
In my settings.py, I set CSRF_USE_SESSIONS to True.
This is my forms.py
class codeForm(forms.Form):
code = forms.IntegerField(label='Question Code')
In my html file, I have this
<form class="card__form" id="code-form" method="POST">
{% csrf_token %} <script type="text/javascript"> // using jQuery
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); </script> {{form.as_p}
<center><input type="submit" class="btn btn-primary card__submit" id="submit_code"></center>
Just before the tag, I have this :
<script>
$(document).ready(function(){
$("#submit_code").click(function(){
alert("Text: ");
event.preventDefault();
var myform = document.getElementById("code-form");
var form = new FormData(this);
form.append('csrfmiddlewaretoken', csrftoken);
$.ajax({
data : form,
dataType:'json',
type: 'POST',
method: 'POST',
url: '{% url 'student:process_code' %}',
contentType: false,
processData: false,
success: function(context) {
alert(context.msg);
},
error: function(context) {
alert(context.msg);
}
});
});
});
</script>
In my views.py
def process_code(request):
context = {}
if request.method == 'POST':
form = codeForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
code = cd.get('code')
print('yay')
if code.isdigit():
The unexpected result was the form is not valid (form.is_valid() = false). Thus, I think my formData object is not converted to a valid forms.Form type.
I also tried to use form = codeForm(request.POST['code']) but it return more error.
How can I get around this? I prefer not to use serialize() because I read that it cannot be used for uploading files which will be my next feature to work on after this has settled. I wanted to use forms.Form because it has cleaned_data method. If you could provide a good solution although not using forms.Form but with good reasoning, I will appreciate it. Thank you so much
try FormData(myform), not "this"

CSRF token mismatch error in laravel 5 on multiple async ajax request?

I'm using Laravel 5.2 for my web application and I have a page with multiple ajax requests by the same event. In $.ajax, I set async: true, Sometimes it shows CSRF token Mismatch error and redirect to login page. However when I set Async: false in ajax, it works fine but it takes lots of time.
Please help me so that it does not show token mismatch error.
in your form create one hidden filed name _token you can use this helper method to generate field
{!! csrf_field() !!}
in javascript you have to fetch this field value
var token = $( "input[name='_token']" ).val();
$.ajax({
method: "POST",
url: "some.php",
data: { name: "John", location: "Boston",_token:token }
});
another way create hidden a span or div add data attribute to it
<div id="token" data-token="{{ csrf_token() }}"></div>
fetch in javascript data value
var token = $( "#token" ).data('token');
$.ajax({
method: "POST",
url: "some.php",
data: { name: "John", location: "Boston",_token:token }
});
You said you use
$.ajaxSetup({ headers: { 'X-CSRF-Token' : $('meta[name=_token]').attr('content') } });
Maybe somewhere headers in request overrides, try to change it on
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name=_token]').attr('content') );
}
});
Please modify your url variable like so :
url: '/my-route'+'?_token=' + '{{ csrf_token() }}',
I think it is not possible to cater this scenario (handing concurrent async requests) in session based CSRF implementation.
Consider 2 async request, r1 and r2:
both are sync and hit at the same time for same session on the CSRF Filter.
r1 changes the CSRF token value which is stored for session id and completes its implementation.
whereas r2 gets TOKEN_MISMATCH since, it has the same old token in the request header which is now get expired by r1 request completion.
So, for r2, CSRF filter will raise error.

How to delete instagram like using getjson post method?

I am trying to delete instagram like . I have valid access token and have the media id of instagram image. I want to use whateverorigin.org so that i dont get cross domain error. when i run the code beleow i dont get any error and even when i press f12 i dont see any error in console!could any one tell me how to fix this getjson?
note:I am looking for non php solution !
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>
function delete() {
var url="https://api.instagram.com/v1/media/xxxxxxxxxxxxxxxxxx_xxxxxxxxxx/likes?access_token=xxxxxxxxxxxx"
$.ajaxSetup({
method: "post"
scriptCharset: "utf-8", //maybe "ISO-8859-1"
contentType: "application/json; charset=utf-8"
});
$.getJSON('http://whateverorigin.org/get?url=' +
encodeURIComponent(url) + '&callback=?',
function(data) {
});
}
</script>
<button onclick="delete()">delete</button>
According to the Instagram API Documentation you need to use the "delete" method. In your code you're using a "post" method, which is creating a like.
So, just change method: "post" to method: "delete"

Resources