phpjqueryajaxsymfonycsrf-token

CSRF Token Validation Issue with Symfony and AJAX with a custom DELETE method


I'm encountering an issue while trying to validate the CSRF token in my Symfony application when using AJAX to delete selected items. Here's an overview of my setup:

When attempting to delete selected items using an AJAX request, I'm receiving an error indicating that the CSRF token is invalid, even though I've ensured that the tokens match between the client and server.

Code HTML Form:

<tbody>
    {% for annonce in allAnnonces %}
    
        <tr class="text-dark" id="annonce_ids{{ annonce.id }}">
            <td>
    
                <input type="checkbox" name="ids" class="checkbox_ids" id="" value="{{ annonce.id }}">
                <input type="hidden" name="_method" value="DELETE">
                <input type="hidden" name="_tokenannonce" value="{{ csrf_token('annonce' ~ annonce.id) }}">
    
            </td>
            <td>
                {{ annonce.marques }} {{ annonce.modeles }} {{ annonce.year }}
            </td>
            <td>
                {{ annonce.price | number_format(0, ',', ' ') }} {{ annonce.device }}
            </td>
            ........
    
            <td>
                <a title="Éditer l'annonce" class="jsvm_cm-actn-btn p-1" href="{{ path('app_compte_annonceur_edit',{'id': annonce.id })}}">
                    <img src="{{ asset('/images/annonces/edit-icon.png')}}" alt="Modifier l'annonce" title="Éditer l'annonce">
                </a>
    
                <form method="POST" action="{{ path('app_compte_annonceur_delete', {id: annonce.id}) }}" style="display: inline-block" onsubmit="return confirm('Êtes vous vraiment sûr ?')">
                    <input type="hidden" name="_method" value="DELETE">
                    <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ annonce.id) }}">
                    <button class="btn" title="supprimer" type="submit">
                        <img src="{{ asset('/images/annonces/delete.png')}}" alt="Supprimer l'annonce" title="Supprimer l'annonce">
                    </button>
                </form>
            </td>
        </tr>
    {% endfor %}
</tbody>
<button class="jsvm_cm-pro-dashb-myveh-btn jsvm_cm-dlt-btn jsvm_multioperation-frontend border btn btn-danger p-2 button red"

        data-for="removemulti"
        data-formid="jsvehiclemanager-list-form"
        id="delete-selected-btn"
        type="submit"
        data-delete-url="{{ path('app_delete_selected') }}">
    <img src="{{ asset('/images/annonces/delete.png')}}"

    <font style="vertical-align: inherit;">
        <font style="vertical-align: inherit;">
            delete selected row</font>
    </font>
    </span>
</button>

Symfony Controller:

#[Route('/dashbord/Annonces/deleteselected', name: 'app_delete_selected', methods: 'DELETE')]
    public function deleteSelected(Request $request): JsonResponse
    {
        $message = [];
        
        $token = $request->get('_tokenannonce');
        $ids= $request->get('ids');
        // dump($data);
        dump($ids);
        dump($token);
        if (!empty($ids)) {
            
            $annonces = $this->annonceRepository->findBy(['id' => $ids]);
            dump($annonces);
            foreach ($annonces as $annonce) {
                
                dump($annonce->getId());
                // dump($this->->getToken('csrf_token')->getValue());
                dump($this->isCsrfTokenValid('annonce'.$annonce->getId(), $token));
                if ($this->isCsrfTokenValid('annonce'.$annonce->getId(), $token)) {
                    $this->em->remove($annonce);
                    $this->em->flush();
                    $this->addFlash('success', 'success');
                    $message = ['success' => true, 'message' => 'Success'];
                } else {
                    $this->addFlash('error', 'Invalid token');
                    $message = ['success' =>false, 'message' => 'Invalid token'];
                }
            }
        dump($message);
        return new JsonResponse(['message' => $message]);
    }

JavaScript AJAX Script:

// Handle multiple selection and row removal
$('#delete-selected-btn').click(function () {
    //var selectedRows = oTable.rows('.selected').data();
    var selectedRows = oTable.rows(".selected").nodes();
    var csrfToken = $('input[name="_tokenannonce"]').val();
    console.log(csrfToken);
    if (selectedRows.length > 0) {
        // Extract IDs from selected rows
       // var selectedIds = selectedRows.toArray().map(row => row[0]); // Assuming the ID is in the first column
        var selectedIds = []; 
         $(selectedRows).each(function () {
           var id = $(this).find("td:first-child input").val(); 
           selectedIds.push(id);
         });
        console.log(selectedIds);
         
        // Send AJAX request to delete selected rows
        console.log($(this).data('delete-url'));
        $.ajax({
          url: $(this).data("delete-url"),
          type: "DELETE",
          dataType: "json",
          processData: true,
          data: {
            ids: selectedIds,
           _tokenannonce: csrfToken
          },
          headers: {
            "_tokenannonce": csrfToken, // Include the CSRF token in the request headers
          },
          traditional: true,
          success: function (response) {
            console.log(response);
            if (response.success) {
              alert("success");
              oTable.rows(".selected").fadeOut().remove().draw(false);
            } else {
              alert("Erreur");
            }
          },
          error: function (xhr, status, error) {
            console.log(error);
            console.log(status);
            console.log(xhr);
            // alert('Erreur lors de la requête Ajax');
          },
          beforeSend: function (data) {
            $(selectedRows).css({
              "background-color": "#ccc",
              color: "#fff", // Change the text color for better visibility
            });
            console.log("Data sent:", data); 
          },
        });
    } else {
        alert('Select a row!');
    }
});

What I've Tried I've double-checked multiple times that the CSRF field name in the form matches the one I'm using in my AJAX request. I've used dump() in Symfony to verify that the CSRF token is correctly retrieved in the controller.

Question Why do I keep receiving an invalid CSRF token error despite all these checks? Is there something I'm missing or should be looking into?


Solution

  • Finally i figure out by doing :

    Controller :

    $tokens = $request->get('_tokenannonce');
    foreach ($annonces as $key => $annonce) {
        $token = $tokens[$key];
        .......
    }
    

    ajax code :

    // Toggle the selected class on row click
    oTable.on('click', 'tbody tr', function () {
        $(this).toggleClass('selected');
    });
    
    // Handle multiple selection and row removal
    $("#delete-selected-btn").click(function () {
    
      var selectedRows = oTable.rows(".selected").nodes();
      var selectedIds = $(selectedRows)
        .map(function () {
          return $(this).find("td:first-child input").val();
        })
        .get();
      var tokens = $(selectedRows)
        .map(function () {
          return $(this).find("td:first-child input[name='_tokenannonce']").val();
        })
        .get();
    
      if (selectedIds.length > 0) {
        $.ajax({
          url: $(this).data("delete-url"),
          type: "DELETE",
          dataType: "json",
          data: {
            ids: selectedIds,
            _tokenannonce: tokens,
          },
          success: function (response) {
            if (response.success) {
              //alert(response.message);
              oTable.rows(".selected").remove().draw(false);
            } else {
              alert(response.message);
            }
          },
          fail: function (xhr, status, error) {
            alert("Error AJAX : " + status + " - " + error);
          },
          beforeSend: function () {
            $(selectedRows).css({
              "background-color": "#ccc",
              color: "#fff",
            });
          },
        });
      
    });