Replies: 6 comments 18 replies
-
I like it! Question – what is the purpose of rendering the CSRF in the form initially at all? Just as a last-ditch fallback if JS is disabled or broken? Of course, that won't help if it's a cached value. I'm working on rolling this into the upcoming version of Craft:
|
Beta Was this translation helpful? Give feedback.
-
I'd also add that rather than arbitrarily targeting the first focusable field as the trigger, you should also ensure that the trigger is a required field - there have been instances where the first focusable field in a given form is not a required field, and as such users can opt to not focus it at all and therefore not get a CSRF refresh. Edit, ah sorry wrote this before fully reading mmikel's comment basically saying the same thing, nevermind. |
Beta Was this translation helpful? Give feedback.
-
How about using the const forms = document.querySelectorAll('form[data-refresh-tokens]');
let refreshed = false;
forms.forEach((form) => {
let request = new XMLHttpRequest();
form.addEventListener('focusin', (e) => {
if (refreshed) return;
request.onload = () => {
let data = JSON.parse(request.response);
// When a user interacts with a form for the first time,
// locate and update tokens in *every* form in the page
forms.forEach((f) => {
// Replace CSFR tokens
let csrf = data.csrf;
let csrfInput = f.querySelector('input[name=' + csrf.name + ']');
if (csrfInput) {
csrfInput.value = csrf.value;
}
});
refreshed = true;
};
// endpoint to get fresh tokens
request.open("GET", '/api/form-tokens');
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.setRequestHeader("HTTP_X_REQUESTED_WITH", "XMLHttpRequest");
request.send();
}, { once: true });
}); https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event |
Beta Was this translation helpful? Give feedback.
-
@markhuot @mmikkel I've been thinking more about this… Instead of worrying about |
Beta Was this translation helpful? Give feedback.
-
I think @mmikkel might be right about Craft going too far here, though I also like the idea of this feature in general. How might it work, for example, if I have a SPA-like site that uses JS-based full-page transitions like Barba JS or similar? When it's my own JS doing the CSRF token refreshing, it's trivial to just fire the function again when the new page has transitioned-in - but when it's someone else's JS that ultimately comes from an unknown location deep within the guts of Craft, it all gets a bit more opaque. Can I/should I call Craft's function again after a page has transitioned? Would I even need to if it uses event delegation on an element that's outside of my |
Beta Was this translation helpful? Give feedback.
-
I've been looking into some similar issues for a project too. Honestly I wonder if the simplest & best solution would be to keep Craft's current naive CSRF-refresh behavior, but provide a way to opt out of having the Could be as simple a change as:
|
Beta Was this translation helpful? Give feedback.
-
A pattern I've been using for some time on static-cached pages with forms is to regenerate CSRF tokens when the user first interacts with a form input. The advantage of this approach, over automatically regenerating tokens on window load, is that it reduces the total number of requests sent from cached pages (since most visitors do not interact with forms) and eliminates the vast majority of automated contact form spam, which will post the stale token present in the static cache and therefore be blocked.
I use this approach to refresh other tokens as well (see https://docs.solspace.com/craft/freeform/v5/templates/caching/#static-page-caching-cdn-blitz), but this is the gist of it:
It would be neat if this was a configurable option for CSRF token regeneration, maybe by passing a selector to
asyncCsrfInputs
, e.g.asyncCsrfInputs="form[data-refresh-tokens]"
.Beta Was this translation helpful? Give feedback.
All reactions