johnathanmdell's avatar

[L5] Ajax File Upload

I needed to create a file upload through Ajax in my application and after an infuriating few hours (due to the encryption on the CSRF token) I came up with this "hack".

    function sendFile(file)
    {
        var formData = new FormData();
        formData.append("image", file);

        var ajax = $.ajax({
            type: 'get',
            url: '/route/to/my/ping/method'
        })
        .done(function() {

            var token = readCookie('XSRF-TOKEN');

            $.ajaxSetup({
                headers: {
                    'X-XSRF-TOKEN': decodeURIComponent(token)
                }
            });

            var ajax = $.ajax({
                type: 'post',
                url: '/route/to/my/upload/method',
                data: formData,
                contentType: false,
                processData: false
            })
            .done(function() {
                alert("success");
            })
            .fail(function() {
                alert("fail");
            });
        });
    }

Edit - Does anybody know of any better ways of achieving the same without wrapping the file input in a form generated by Illuminate Form Builder?

0 likes
11 replies
tios's avatar

You could use blueimp's fileupload for jquery.

{!! Form::open( [ 'route' => ['productimage.upload', $p ], 'method' => 'POST', 'id' => 'uploadForm', 'files' => true ] ) !!}
<input id="fileupload" type="file" name="file" data-url="{{ route( 'productimage.upload', array( 'products_id'  => $p->products_id ) ) }}">
{!! Form::close() !!}
...
$('#fileupload').fileupload({
        dataType: 'json',
        done: function (e, data) {
            console.log(data);
        }
    });

1 like
johnathanmdell's avatar

Sorry I should have been a bit more clear, I want to accomplish it without Illuminate Form Builder or Blade for that matter.

MThomas's avatar

@41eight you can simply 'translate' the blade syntax to regular html there is nothing in the js that depends on the Form Builder or Blade.

johnathanmdell's avatar

@MThomas I think you misunderstood me or I wasn't too clear, the CSRF token is very much dependant on the Form Builder as it is automatically inserted when creating a form or when using Form::token() or csrf_token(). However, if you created a form outside of Laravel with just some HTML and had just a bit of JavaScript, submitting that form it would fail because there was no CSRF token.

Thus, the hack to get the token first before submitting the file upload through Ajax.

1 like
jakeryansmith's avatar

All the Form builder does is add a hidden field <input name="_token" type="hidden" value="yourtokenhere"> You could just add this to a form you make with pure html.

2 likes
nolros's avatar

When you say hack? What is the problem? Publishing the token to JS? Getting the token out of JS? Submitting the token? Unsure what you see as the problem. I've written a ton of AngularJS code and have worked around security CSRF issues in a number of ways, including writing my own.

1 like
mibu31's avatar

Forgive me as I'm not currently using L5 but... hopefully this will help (this is how I've done it in the past)

http://words.weareloring.com/development/laravel/laravel-4-csrf-tokens-when-using-jquerys-ajax/

Set a meta tag called "_token" using csrf_token(). You can then tell jQuery to pass this token as a custom header for AJAX requests. Modify your CSRF filter - if the request is an AJAX request, tell it to retrieve the token from the request header rather than Input::get().

1 like
johnathanmdell's avatar

@jakeryansmith, @MThomas, @mibu31, thank you for the replies however as per my example, csrf_token only works inside of Laravel, if I had a form outside of Laravel that needed to post to a route I would have no way to pull in the token.

@mibu31 just to add to your answer, adding csrf_token to your header won't work in L5 as it needs to be encrypted to be matched correctly and the method csrf_token gives an unencrypted token.

@nolros the problem is posting to Laravel from outside of Laravel, perfect example is an SPA. The hack allows you to do a "ping" to Laravel to fetch the encrypted token and then add it to the headers for the post.

pmall's avatar

@41eight The purpose of csrf protection is to forbid one to send form data from other domain.

Ah you dont have to decode the cookie to get the csrf hash value. form_data.append('_token', crsf_hash_value) is enough.

nolros's avatar
  1. place
<input id="mme-token" type="hidden" name="_token" value="<?php echo csrf_token(); ?>">

in your footer or wherever you want on your page.

  1. Jquery, or can write it JS or use a Angular directive, I have all 3 if you want, function
    var getToken = function(){
        return $('#mme-token').prop('value');
    };
  1. I place this in my main JS file, in my case my main Angular controller.
  2. whatever object you have that you are submitting, lets say you have an invoice object, invoice = {};
  3. add invoice = {_token: getToken()}. I obviously first check if it blank or empty first.
  4. then submit the data object to your route using $.ajax
  5. For file uploads I use multi-file upload even if I'm using single file upload.
<form method="post" action="upload-page.php" enctype="multipart/form-data">
  <input name="filesToUpload[]" id="filesToUpload" type="file" multiple="" />
</form>

I then capture that event then do the same and add the token to the array:

filesToUpload[filesToUpload.length+1] = {'_token':getToken()};

Bottom line is you need to add key _token with the Session::token value when submitting the form.

Does this help?

Nolan

Please or to participate in this conversation.