Stateless CSRF protection outside forms #59851
|
I don't use Turbo to handle form submissions. I use Axios or Fetch. I would like to leverage stateless protection without Symfony form. On client side I could use the code from csrf_protection_controller.js, but how do I check the validity on the server side? |
Replies: 3 comments 1 reply
|
Just use the same I wrote an article explaining how stateless & stateful CSRF protection works in-depth in Symfony because I wanted to know myself. If you're confused, this should help. |
|
What I want could be achieved by manually calling the functions in the https://github.com/symfony/recipes/blob/main/symfony/stimulus-bundle/2.20/assets/controllers/csrf_protection_controller.js before I make a new axios request. I would also need to add my custom token name into |
|
Here's how I made it work: framework:
form:
csrf_protection:
token_id: submit
csrf_protection:
check_header: true
stateless_token_ids:
- submit
- authenticate
- logout
- ajax_stateless// Stateless CSRF
// based on csrf_protection_controller.js
const csrfCokieName = 'csrf-token';
axiosInstance.interceptors.request.use(
config =>
{
config.statelessCsrfToken = generateCsrfTokenAndSetCookie();
config.headers[csrfCokieName] = config.statelessCsrfToken;
return config;
},
(error) =>
{
removeCsrfCookie(error.config.statelessCsrfToken);
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.use(
response =>
{
removeCsrfCookie(response.config.statelessCsrfToken);
return response;
},
(error) =>
{
removeCsrfCookie(error.config.statelessCsrfToken);
return Promise.reject(error);
}
);
const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;
function generateCsrfTokenAndSetCookie()
{
let csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
if (tokenCheck.test(csrfToken))
{
const cookie = csrfCokieName + '_' + csrfToken + '=' + csrfCokieName + '; path=/; samesite=strict';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
return csrfToken;
}
function removeCsrfCookie(csrfToken)
{
if (tokenCheck.test(csrfToken))
{
const cookie = csrfCokieName + '_' + csrfToken + '=0; path=/; samesite=strict; max-age=0';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}$token = $request->headers->get('CSRF-TOKEN');
if (!$this->csrfTokenManager->isTokenValid(new CsrfToken('ajax_stateless', $token)))
{
throw new AccessDeniedException('Invalid stateless CSRF token.');
}After testing, the profiler log contained: |
Here's how I made it work: