Cloudflare Turnstile 서버 검증: Siteverify와 토큰 만료·중복 처리
Turnstile 토큰을 서버에서 검증하고 만료, 중복 제출, 테스트 키와 멱등성의 경계를 확인한다.
Cloudflare Turnstile 위젯이 성공 표시를 보여도 그것만으로 폼 제출을 신뢰할 수 없다. 브라우저에서 받은 토큰을 서버가 Siteverify API로 검증하고 성공 응답을 확인한 다음에만 가입·문의 등 보호할 작업을 실행한다.
브라우저의 토큰을 서버에서 검증하는 과정을 표현한 AI 생성 일러스트
토큰은 서버에서 검증한다
위젯은 기본적으로 폼에 cf-turnstile-response 필드를 넣는다. 서버는 이 값을 받아 비밀 키와 함께 Siteverify에 POST한다. 사이트 키는 브라우저에 공개되지만 비밀 키는 서버 환경변수 등 안전한 저장소에 두고 클라이언트 코드나 로그에 넣지 않는다. 서버 엔드포인트는 위젯을 거치지 않은 직접 요청도 받을 수 있으므로 클라이언트의 성공 콜백만 확인해서는 안 된다.
검증 예제와 처리 경계
다음은 Node.js에서 사용할 수 있는 설명용 함수다. example.com은 실제 위젯의 허용 호스트로 교체하고, 비밀 키는 서버 설정에서 읽는다. 호출자는 폼에서 받은 토큰을 전달하고 반환값이 true일 때만 작업을 수행한다.
이 함수는 타임아웃·통신 오류·HTTP 실패·검증 실패를 모두 거절한다. 운영에서는 사용자에게 다시 시도할 수 있음을 알리고, 오류 유형을 토큰이나 비밀 키 없이 기록한다. 검증 응답의 hostname을 예상 호스트와 정확히 비교하고, 위젯에 action을 설정했다면 응답의 action도 서버에서 예상 값과 비교한다. 이 코드는 action을 설정하지 않은 예제다. 폼 입력 검증, 인증·인가, CSRF 방어와 요청 제한도 별도로 필요하다. 응답 필드와 오류 코드
만료와 중복 제출
토큰은 발급 후 300초 동안 유효하며 한 번의 검증을 위해 만들어진다. 오래 열린 폼이나 제출 재시도에서 timeout-or-duplicate가 나오면 위젯을 turnstile.reset()으로 초기화해 새 토큰을 받는다. 네트워크 오류로 검증 결과가 불확실한 경우 Siteverify는 재시도용 UUID idempotency_key를 지원한다. 위 함수는 자동 재시도하지 않는다.
Cloudflare에 따르면 토큰 사용 상태의 전파는 최종 일관성을 가지므로 서로 가까운 중복 검증이 모두 성공할 수 있다. 따라서 결제·주문 등 중복 실행 방지는 토큰의 단일 사용 속성에 의존하지 말고, 서버에서 작업별 멱등 키와 원자적 상태 변경으로 별도 구현한다. 토큰 재사용과 네트워크 전파
테스트와 운영 전 확인
공식 테스트 키로 성공, 실패, 중복 토큰 분기를 시험할 수 있다. 테스트 사이트 키가 만든 더미 토큰은 테스트 비밀 키로 검증해야 하며 운영 키와 섞을 수 없다. 테스트 성공 응답의 hostname은 항상 example.com이고 action과 cdata는 포함되지 않으므로 실제 호스트·action 검사는 별도의 실제 키 환경에서 확인한다. 테스트 키는 운영에 배포하지 않는다.
2026년 11월 14일 공식 문서 기준이다. 위 코드는 문서 기반 설명용이며 실제 Turnstile 서비스에 요청해 실행하지 않았다.
English version:
Cloudflare Turnstile Server Validation: Siteverify, Expiration, and Duplicate Submissions