Skip to main content

token-lifecycle

Handle token generation, expiration, validation workflow, and form integration. Activate when implementing form submission with CAPTCHA, handling token expiration, or integrating with server-side validation.

来源信息

仓库
marsidev/react-turnstile
最近来源活动
2026年3月20日 21:20
检测到的 SKILL.md 语言
英语
星标
854
分支
47

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
token-lifecycle
description
Handle token generation, expiration, validation workflow, and form integration. Activate when implementing form submission with CAPTCHA, handling token expiration, or integrating with server-side validation.
triggers
["turnstile token expired","turnstile getResponse","turnstile reset","turnstile form submit","turnstile onSuccess","turnstile onExpire","validate turnstile token","turnstile server validation"]
category
lifecycle
metadata
{"library":"@marsidev/react-turnstile","library_version":"1.4.2","framework":"React"}
# Token Lifecycle Handle token generation, expiration, validation workflow, and form integration. ## Understanding Tokens Turnstile tokens are **single-use** and expire after a timeout (typically 5 minutes). Once validated by your server, they cannot be used again. ## Three Ways to Get Tokens ### 1. onSuccess Callback (Recommended) Get the token when the user completes the challenge: ```tsx import { Turnstile } from '@marsidev/react-turnstile' import { useState } from 'react' export default function ContactForm() { const [token, setToken] = useState<string | null>(null) return ( <form> <input type="email" placeholder="Email" /> <Turnstile siteKey="YOUR_SITE_KEY" onSuccess={(token) => setToken(token)} /> <button type="submit" disabled={!token}> Submit </button> </form> ) } ``` ### 2. ref.current.getResponse() Get the token imperatively at submission time: ```tsx import { Turnstile } from '@marsidev/react-turnstile' import type { TurnstileInstance } from '@marsidev/react-turnstile' import { useRef } from 'react' export default function ContactForm() { const ref = useRef<TurnstileInstance>(null) async function handleSubmit(e: React.FormEvent) { e.preventDefault() // Get token at the moment of submission const token = ref.current?.getResponse() if (!token) { alert('Please complete the CAPTCHA') return } await submitForm(token) } return ( <form onSubmit={handleSubmit}> <input type="email" placeholder="Email" /> <Turnstile ref={ref} siteKey="YOUR_SITE_KEY" /> <button type="submit">Submit</button> </form> ) } ``` ### 3. Hidden Form Field The widget automatically adds a hidden input. Access it via FormData: ```tsx export default function ContactForm() { async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault() const formData = new FormData(e.currentTarget) const token = formData.get('cf-turnstile-response') if (!token) { alert('Please complete the CAPTCHA') return } await submitForm(token) } return ( <form onSubmit={handleSubmit}> <input type="email" name="email" placeholder="Email" /> <Turnstile siteKey="YOUR_SITE_KEY" /> <button type="submit">Submit</button> </form> ) } ``` ## Complete Form Integration Best practice: Get token at submission time, validate server-side, reset widget: ```tsx 'use client' import { Turnstile } from '@marsidev/react-turnstile' import type { TurnstileInstance } from '@marsidev/react-turnstile' import { useRef, useState } from 'react' export default function ContactForm() { const ref = useRef<TurnstileInstance>(null) const [isSubmitting, setIsSubmitting] = useState(false) const [error, setError] = useState<string | null>(null) async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault() setError(null) const token = ref.current?.getResponse() if (!token) { setError('Please complete the CAPTCHA') return } setIsSubmitting(true) try { const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, /* form data */ }) }) if (!response.ok) { throw new Error('Submission failed') } // Success! Reset widget for potential re-submission ref.current?.reset() // Clear form, show success message, etc. } catch (err) { setError('Failed to submit. Please try again.') // Reset widget on error too (token may be expired/used) ref.current?.reset() } finally { setIsSubmitting(false) } } return ( <form onSubmit={handleSubmit}> {error && <div className="error">{error}</div>} <input type="email" name="email" placeholder="Email" required /> <textarea name="message" placeholder="Message" required /> <Turnstile ref={ref} siteKey="YOUR_SITE_KEY" onExpire={() => { // Optional: handle expiration console.log('Token expired, please retry') }} /> <button type="submit" disabled={isSubmitting}> {isSubmitting ? 'Sending...' : 'Send Message'} </button> </form> ) } ``` ## Handling Token Expiration Tokens expire after ~5 minutes. Handle this with `onExpire` callback: ```tsx <Turnstile ref={ref} siteKey="YOUR_SITE_KEY" options={{ refreshExpired: 'manual' }} onExpire={() => { // Token expired - inform user or auto-reset alert('Verification expired. Please try again.') ref.current?.reset() }} /> ``` Or let it auto-refresh: ```tsx <Turnstile siteKey="YOUR_SITE_KEY" options={{ refreshExpired: 'auto' }} // Default /> ``` ## Server-Side Validation **Important:** The library provides TypeScript types but NO built-in validation. You must implement server-side validation yourself. ### Example API Route (Next.js) ```tsx // app/api/verify/route.ts import { NextResponse } from 'next/server' export async function POST(request: Request) { const { token } = await request.json() const verification = await fetch( 'https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ secret: process.env.TURNSTILE_SECRET_KEY!, response: token, }), } ) const result = await verification.json() if (!result.success) { return NextResponse.json( { error: 'CAPTCHA validation failed' }, { status: 400 } ) } return NextResponse.json({ success: true }) } ``` ### Environment Variables ```bash # .env.local (server-side only) TURNSTILE_SECRET_KEY=0x0000000000000000000000000000000000000000000 ``` **Never expose the secret key client-side!** ## Imperative API Methods Control the widget programmatically: ### reset() Reset the widget after submission or error: ```tsx ref.current?.reset() ``` ### getResponsePromise() Wait for token with timeout (useful for programmatic flows): ```tsx try { const token = await ref.current?.getResponsePromise(30000) // 30s timeout // Use token... } catch (error) { // Timeout or widget error } ``` ### isExpired() Check if token has expired: ```tsx if (ref.current?.isExpired()) { ref.current?.reset() } ``` ### remove() and render() Advanced: Fully remove and re-render the widget: ```tsx ref.current?.remove() // Remove from DOM ref.current?.render() // Re-render (only if previously removed) ``` ## Common Mistakes ### ❌ Token Expires Before Form Submission **Problem:** User takes too long to submit, token expires. **Wrong:** ```tsx const [token, setToken] = useState<string | null>(null) <Turnstile onSuccess={setToken} /> // User delays submitting... <button onClick={() => submit(token)}>Submit</button> // Token expired! ``` **Correct:** ```tsx // Get token at submission time, not onSuccess const token = ref.current?.getResponse() await submit(token) ref.current?.reset() // Reset for next time ``` ### ❌ Expecting Built-in Server Validation **Wrong:** ```tsx import { validateTurnstile } from '@marsidev/react-turnstile' // ❌ This doesn't exist! ``` **Correct:** ```tsx // Implement your own server validation const response = await fetch('/api/verify', { method: 'POST', body: JSON.stringify({ token }) }) ``` ### ❌ Calling Methods Before Widget Loads **Wrong:** ```tsx useEffect(() => { // Widget not ready yet! const token = ref.current?.getResponse() // undefined }, []) ``` **Correct:** ```tsx // Wait for onSuccess or user action <Turnstile ref={ref} onSuccess={(token) => { // Widget is ready }} /> ``` ### ❌ Not Resetting After Validation **Problem:** Token is single-use. After server validation, it's invalid. **Correct:** ```tsx const result = await validateToken(token) if (result.success) { // Process form... ref.current?.reset() // Reset for potential re-submission } ``` ## Best Practices 1. **Get token at submission time** - Not onSuccess callback 2. **Always reset after use** - Tokens are single-use 3. **Handle expiration gracefully** - Inform users and provide retry 4. **Validate server-side** - Never trust client-side validation alone 5. **Keep secret key server-side** - Never expose in client code ## See Also - [basic-setup skill](./basic-setup/SKILL.md) - Basic widget setup - [multiple-widgets skill](./multiple-widgets/SKILL.md) - Managing tokens with multiple widgets - [nextjs-ssr skill](./nextjs-ssr/SKILL.md) - Server Actions validation
在 GitHub 查看