Cloudflare Turnstile NextJS: Invalid Token Error on Repeated Submissions

Was used in notlink — a blazingly fast url shortener ever built with rust programming language. Check: https://notl.ink The “Invalid token” error occurs because the Turnstile token is being reused for multiple submissions. Cloudflare Turnstile tokens are single-use; once validated, they can’t be used again. Here’s how to fix it: Reset the Turnstile widget after each submission Update your frontend code to reset the Turnstile widget and clear the token state after submission: // Add a ref to the Turnstile component const turnstileRef = useRef(); async function handleShorten(val: string) { // ... existing code ... try { const response = await fetch(/api/shorten, { /* ... */ }); const data: ShortURLResponse = await response.json(); // Reset Turnstile after successful submission turnstileRef.current?.reset(); setTurnstileToken(""); setTurnstileStatus("required"); } catch (error) { console.error('Error:', error); } finally { setLoading(false); // Ensure Turnstile is reset even if there's an error turnstileRef.current?.reset(); setTurnstileToken(""); setTurnstileStatus("required"); } } // Update your Turnstile component with the ref ref={turnstileRef} siteKey={process.env.TURNSTILE_SITE_KEY!} // ... other props ... /> (Optional) Clear token state on expiration/error Enhance your Turnstile event handlers: // ... other props ... onExpire={() => { setTurnstileStatus("expired"); setTurnstileError("Security check expired. Please verify again."); setTurnstileToken(""); // Clear expired token }} onError={() => { setTurnstileStatus("error"); setTurnstileError("Security check failed. Please try again."); setTurnstileToken(""); // Clear invalid token }} /> Why this works: Each submission now requires a fresh Turnstile verification The token state is cleared after submission/errors/expiration The Turnstile widget is reset to force a new challenge Additional recommendations for your backend: Ensure your idempotencyKey implementation matches Turnstile's requirements Consider adding rate limiting to prevent abuse Verify the token expiration time (typically 5 minutes) By implementing these changes, users will need to complete a new Turnstile verification for each submission, preventing token reuse and the “Invalid token” error.

Feb 20, 2025 - 18:24
 0
Cloudflare Turnstile NextJS: Invalid Token Error on Repeated Submissions

Was used in notlink — a blazingly fast url shortener ever built with rust programming language. Check: https://notl.ink

The “Invalid token” error occurs because the Turnstile token is being reused for multiple submissions. Cloudflare Turnstile tokens are single-use; once validated, they can’t be used again. Here’s how to fix it:

  1. Reset the Turnstile widget after each submission

Update your frontend code to reset the Turnstile widget and clear the token state after submission:

// Add a ref to the Turnstile component
const turnstileRef = useRef();

async function handleShorten(val: string) {
// ... existing code ...

try {
const response = await fetch(/api/shorten, { /* ... */ });
const data: ShortURLResponse = await response.json();

// Reset Turnstile after successful submission
turnstileRef.current?.reset();
setTurnstileToken("");
setTurnstileStatus("required");

} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
// Ensure Turnstile is reset even if there's an error
turnstileRef.current?.reset();
setTurnstileToken("");
setTurnstileStatus("required");
}
}

// Update your Turnstile component with the ref
ref={turnstileRef}
siteKey={process.env.TURNSTILE_SITE_KEY!}
// ... other props ...
/>

  1. (Optional) Clear token state on expiration/error

Enhance your Turnstile event handlers:

// ... other props ...
onExpire={() => {
setTurnstileStatus("expired");
setTurnstileError("Security check expired. Please verify again.");
setTurnstileToken(""); // Clear expired token
}}
onError={() => {
setTurnstileStatus("error");
setTurnstileError("Security check failed. Please try again.");
setTurnstileToken(""); // Clear invalid token
}}
/>
Why this works:

Each submission now requires a fresh Turnstile verification
The token state is cleared after submission/errors/expiration
The Turnstile widget is reset to force a new challenge
Additional recommendations for your backend:

Ensure your idempotencyKey implementation matches Turnstile's requirements
Consider adding rate limiting to prevent abuse
Verify the token expiration time (typically 5 minutes)
By implementing these changes, users will need to complete a new Turnstile verification for each submission, preventing token reuse and the “Invalid token” error.