3
votes

This is a classic issue with ajax request with enabled SecurityComponent. I have mainly a SPA. That's the main problem.

I'm using also the CSRF component, which works fine:

const response = await axios.post("/items/add.json", data, {
  headers: {"X-CSRF-Token": "<?= $this->getRequest()->getParam('_csrfToken') ?>"}
});

What doesnt work is to send the _Token for the security component:

{message: "'_Token' was not found in request data.", url: "/.../add.json", code: 400,…}

Of course I can disable the SecurityComponent.

I don't need a form/form-helper for my request, then the question is if it makes sense to use the SecurityComponent at all here when I don't use a traditional form based application. Of course it makes sense that I expect certain post fields/values in certain actions but I'm not sure how to use that in conjunction with the SecurityComponent.

I guess I could create a dummy form with the helper and extract the tokens from there, but this is only generated once and I have a SPA.

This works of course:

beforeFilter: $this->getEventManager()->off($this->Security);

1
The CSRF token and security token are two completely different things. The latter is to confirm that the form hasn't been tampered with (e.g. changing hidden fields). To date, I think the common solution has been to simply disable the security component for Ajax posts. :-( - Greg Schmidt
Ok, then there are now 3 tokens, csrf, security and debug token. I also saw those other "solutions" disabling security, but that's not a real option. I wanted to avoid looking into the source, but seems like I have to. - Sam
What's your threat model, and what exactly are you trying to do? The only way to have the security component work together with AJAX, is when you're sending the exact form data/structure as it has been / would have been generated by the form helper. The whole concept of securing fields can only work when the server is the only place where a token can be generated, you cannot have that in the frontend, that would break the whole concept and provide no security. - ndm
@ndm When you use the form helper (which I don't need here) then you can see the security _Tokens. Of course it's encrypted, still you have to send the expected token back. - Sam
Sure, I didn't mean to say that you can't have the token in the frontend (you must have it there), I ment to say that you can't have token generation in the frontend, ie when using the security component, your frontend form data structure is being dictated by the backend. You don't have to use the form helper, but you must generate the token in the backend, hence I'm asking what exactly you're trying to protect against, and what your doing in the frontend, ie how is the data structure being built?, what does it look like?, does it have dynamically added/removed fields? etc. - ndm

1 Answers

0
votes

I'm only offering a technical answer, not opining on whether it makes sense to use the SecurityComponent - security is not my forte.

If you need to submit many AJAX calls from your SPA, can you generate the request forms through AJAX requests, then pull the tokens out of them? It might sound a bit hacky, but could actually be done quite neatly - each AJAX request you submit could do what you need it to do, then return a new <form> element (complete with new tokens) as part of its response - which you can then stash somewhere non-visible, overwriting the previous one and retrieve tokens from for your next call.

When it comes to then managing the submission, I have found the easiest way to deal with tokens etc is to combine this answer with serialization (e.g. jQuery's serialize()). This packages up all the hidden form values neatly. My (jQuery-based) code looks like this:

var ele_form = $('#some-form-id');
var ele_csrf = ele_form.find('input[name="_csrfToken"]');
var target_url = ele_form.attr('action'); // form-action is validated, so need to generate this correctly
var csrf_token = ele_csrf.val();
$.ajax({
    type: 'POST',
    url: target_url,
    beforeSend: function(xhr){
        xhr.setRequestHeader('X-CSRF-Token', csrf_token);
    },
    data: ele_form.serialize()
});

Happy to elaborate on the example if you want to be more specific about what kind of requests you're sending in your SPA.