I am working on a web application using Symfony for the backend and VueJS 3.2 for the frontend, and I am facing a persistent issue with CSRF token validation. Despite several attempts and checks, I keep receiving an "Invalid CSRF token" error when trying to validate the token in Symfony. I would appreciate any insights or suggestions to resolve this issue.
Environment:
- Backend: Symfony 6.3
- Frontend: VueJS 3.2
- Method of sending data: FormData in a POST request
Problem:
When submitting a registration form from the VueJS frontend, including a CSRF token retrieved from Symfony, the backend validation consistently fails with an "Invalid CSRF token" error.
Backend Implementation:
In Symfony, I have set up a route to generate and send a CSRF token to the frontend:
namespace App\Controller;
use App\Entity\User;
use App\Form\UserRegistrationType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
class RegistrationController extends AbstractController
{
private CsrfTokenManagerInterface $csrfTokenManager;
public function __construct(CsrfTokenManagerInterface $csrfTokenManager){
$this->csrfTokenManager = $csrfTokenManager;
}
#[Route('/api/get-csrf-token', name: 'get-csrf-token', methods: ['GET'])]
public function getCsrfToken(): JsonResponse
{
$csrfToken = $this->csrfTokenManager->getToken('csrf_token')->getValue();
return $this->json(['csrf_token' => $csrfToken]);
}
#[Route('/api/register', name: 'app_register', methods: ['POST'])]
public function register(Request $request, UserPasswordHasherInterface $passwordHasher): Response
{
$content = $request->request->all();
$csrfToken = $request->get('_token', '');
// return $this->json(['message' =>$csrfToken]);
if (!$this->isCsrfTokenValid('csrf_token', $csrfToken)) {
return $this->json(['errors' => 'ERROR: The CSRF TEST token is invalid. Please try to resubmit the form.'], Response::HTTP_BAD_REQUEST);
}
$user = new User();
$form = $this->createForm(UserRegistrationType::class, $user);
$form->submit($content);
if ($form->isSubmitted() && $form->isValid()) {
// Hashing the password
$hashedPassword = $passwordHasher->hashPassword($user, $user->getPassword());
$user->setPassword($hashedPassword);
// Save the User entity in the database
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
// Return a success response
return $this->json(['message' => 'User successfully registered.'], Response::HTTP_CREATED);
}
// Handle form errors
return $this->json(['errors' => (string) $form->getErrors(true, false)], Response::HTTP_BAD_REQUEST);
}
Frontend Implementation (VueJS):
On the VueJS side, I am sending the token back to Symfony in a POST request using FormData:
<template>
<div class="form-container">
<form @submit.prevent="submitForm" class="form-register">
<div class="form-group">
<label for="email">Email</label>
<input v-model="user.email" type="email" id="email" placeholder="Email" class="form-control" required >
</div>
<div class="form-group">
<label for="password">Password</label>
<input v-model="user.password" type="password" id="password" placeholder="Password" class="form-control" required>
</div>
<input type="hidden" name="_token" :value="csrfToken">
<button type="submit" class="btn-submit">Register</button>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
user: {
email: '',
password: '',
},
errors: [],
csrfToken: '',
};
},
created() {
axios.get('/get-csrf-token').then((response) => {
this.csrfToken = response.data.csrf_token;
console.log('Token Get:', this.csrfToken);
});
},
methods: {
async submitForm() {
try {
let formData = new FormData();
Object.keys(this.user).forEach(key => formData.append(key, this.user[key]));
formData.append('_token', this.csrfToken);
const config = {
headers: {
'Content-Type': 'multipart/form-data'
}
};
const response = await axios.post('/register', formData, config);
console.log(response.data);
} catch (error) {
if (error.response) {
console.error('Error:', error.response.data);
} else {
console.error('Error:', error.message);
}
}
}
},
};
</script>
Issue:
Every time I submit the form, Symfony's CSRF token validation fails, and I receive an "Invalid CSRF token" error, despite ensuring that the token sent from VueJS is identical to the one generated by Symfony.
What I've Tried:
Verifying the consistency of the CSRF token ID used in generation and validation. Ensuring that the token is correctly included in the FormData and sent in the POST request. Checking the request and response headers for any discrepancies. Testing the functionality in a controlled environment with a simple Symfony form. I am not sure what I am missing or doing incorrectly. Any guidance on how to troubleshoot this issue or insights into what might be causing the token validation to fail would be greatly appreciated.
$form = $this->createForm(UserRegistrationType::class, $user, ["csrf_protection" => false]);