laracasts.wyllie's avatar

CSRF token mismatch

How can I append the ajax property on H5P Standalone (https://github.com/tunapanda/h5p-standalone) an header for an X-CSRF-TOKEN?


const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
                        
const el = document.getElementById('h5p-container');
const options = {
                            h5pJsonPath: 'h5p-content/true-false-question-34806/',
                            frameJs: 'h5p/frame.bundle.js',
                            frameCss: 'h5p/styles/h5p.css',
                            postUserStatistics: true,
                            ajax: {
                                setFinishedUrl: "{{ route('h5p.finish') }}",

                                // THIS DOES NOT WORK 
                                headers: {
                                    'X-CSRF-TOKEN': csrfToken, // Use the fetched CSRF token here
                                    '_method': 'patch',
                                },

                            },
                        };                        
new H5PStandalone.H5P(el, options);

For testing this workarround worked for me in Laravel:

<script>
                        $.ajax({
                            url: "{{ route('h5p.finish') }}",
                            type: 'POST', 
                            data: {
                                contentId: 'lykvh3vvf',
                                score: 1,
                                maxScore: 1,
                                opened: 1710971851,
                                finished: 1710971854
                            },

                            // THIS WORK 
                                headers: {
                                    'X-CSRF-TOKEN': csrfToken, // Use the fetched CSRF token here
                                    '_method': 'patch',
                                },

                        });
 </script>

On the first I got this CSRF token mismatch Error: { "message": "CSRF token mismatch.", "exception": "Symfony\Component\HttpKernel\Exception\HttpException", "file": "/Users/chris/code/mk25/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php", "line": 632, "trace": [

0 likes
2 replies
LaryAI's avatar
Level 58

It seems like you're trying to set the CSRF token in the headers for an AJAX request within the H5P standalone options. However, the way you're setting the headers might not be correctly interpreted by the H5P standalone library or the underlying AJAX mechanism it uses.

To ensure that the CSRF token is included in the headers of the AJAX request, you can try the following approach:

  1. Fetch the CSRF token from the meta tag as you've done.
  2. Modify the ajax property in the options object to include a beforeSend function. This function is called before the AJAX request is sent, allowing you to modify the headers.

Here's how you can modify your code:

const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

const el = document.getElementById('h5p-container');
const options = {
    h5pJsonPath: 'h5p-content/true-false-question-34806/',
    frameJs: 'h5p/frame.bundle.js',
    frameCss: 'h5p/styles/h5p.css',
    postUserStatistics: true,
    ajax: {
        setFinishedUrl: "{{ route('h5p.finish') }}",
        // Add beforeSend function to set headers
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-CSRF-TOKEN', csrfToken);
            xhr.setRequestHeader('_method', 'patch');
        },
    },
};

new H5PStandalone.H5P(el, options);

Please note that the beforeSend function receives the xhr object, which represents the XMLHttpRequest. You can use this object to set request headers.

Make sure that the H5PStandalone library supports the beforeSend function in its AJAX configuration. If it doesn't, you may need to modify the library itself or submit a feature request to the maintainers to support custom headers in AJAX requests.

Also, ensure that the route you're posting to (h5p.finish) is expecting a PATCH request and that the CSRF token is valid for the session. If the token is not valid, you will continue to receive a CSRF token mismatch error.

laracasts.wyllie's avatar

Thank you for your idea, but it didn't work either.

I found another way. This is working:

// Step 1: Fetch the CSRF token using plain JavaScript
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

// Step 2: Define your options, including the CSRF token in the headers
const el = document.getElementById('h5p-container');
const options = {
    h5pJsonPath: 'h5p-content/true-false-question-34806/',
    frameJs: 'h5p/frame.bundle.js',
    frameCss: 'h5p/styles/h5p.css',
};

new H5PStandalone.H5P(el, options).then(function () {
    H5P.externalDispatcher.on("xAPI", (event) => {

        // Assuming `event` is the xAPI event object you've received
        const xAPIStatement = event.data.statement;

        // Check if the result object exists
        if (xAPIStatement.result) {
            // The result object exists, now you can safely access properties
            if (xAPIStatement.result.completion === true) {
                // console.log('The action was completed.');
                // Execute further code here that should run when completion is true

                // Reading out the result
                // console.log('Completion:', xAPIStatement.result.completion);
                // console.log('Success:', xAPIStatement.result.success);
                // console.log('Duration:', xAPIStatement.result.duration);
                // console.log('Response:', xAPIStatement.result.response);

                // Reading out the score
                // console.log('Score Raw:', xAPIStatement.result.score.raw);
                // console.log('Score Min:', xAPIStatement.result.score.min);
                // console.log('Score Max:', xAPIStatement.result.score.max);

                $.ajax({
                    url: "{{ route('h5p.finish') }}",
                    type: 'POST', // or 'PUT'
                    data: {
                        contentId: 'lykvh3vvf',
                        score: xAPIStatement.result.score.raw,
                        maxScore: xAPIStatement.result.score.max,
                        opened: 1710971851,
                        finished: 1710971854
                    },
                    headers: {
                        'X-CSRF-TOKEN': csrfToken, // Use the fetched CSRF token here
                        '_method': 'patch',
                    },

                });



            } else {
                // console.log('The action was not completed or completion is false.');
                // Execute code here that should run when completion is not true
            }
        } else {
            // The result object does not exist in this xAPI statement
            // console.log('The xAPI statement does not contain a result object.');
        }

    });
});
1 like

Please or to participate in this conversation.