Loading...
Loading...
> Detect and block automated bot traffic with multiple protection strategies.
Bot protection prevents automated attacks on your forms and APIs. Use honeypot fields, timing validation, user-agent analysis, and CAPTCHA to block bots while keeping the experience smooth for real users.
DESC: Configure Cloudflare Turnstile (optional)
1$# .env.local23$# Cloudflare Turnstile (optional)4$NEXT_PUBLIC_TURNSTILE_SITE_KEY="your-site-key"5$TURNSTILE_SECRET_KEY="your-secret-key"
Add invisible fields that bots will fill but humans won't
1// Client-side form component2"use client";34export function ContactForm() {5 return (6 <form action="/api/contact" method="POST">7 <input type="text" name="name" placeholder="Name" required />8 <input type="email" name="email" placeholder="Email" required />910 {/* Honeypot - hidden from users, bots will fill it */}11 <input12 type="text"13 name="website"14 style={{15 position: "absolute",16 left: "-9999px",17 tabIndex: -1,18 autocomplete: "off",19 }}20 aria-hidden="true"21 />2223 {/* Alternative CSS hiding */}24 <div className="hidden">25 <input type="text" name="phone_number" tabIndex={-1} />26 </div>2728 <button type="submit">Send</button>29 </form>30 );31}3233// Server-side validation34// src/app/api/contact/route.ts3536import { NextRequest, NextResponse } from "next/server";3738export async function POST(request: NextRequest) {39 const body = await request.formData();4041 // Check honeypot fields42 const honeypot1 = body.get("website");43 const honeypot2 = body.get("phone_number");4445 if (honeypot1 || honeypot2) {46 // Log potential bot attempt47 console.log("Bot detected: honeypot filled");4849 // Return success to not alert the bot50 return NextResponse.json({ success: true });51 }5253 // Process legitimate submission54 const name = body.get("name");55 const email = body.get("email");5657 // ...58}
Reject submissions that are too fast (bots) or too slow (stale tokens)
1// Client-side: Add timestamp to form2"use client";34import { useEffect, useState } from "react";56export function ProtectedForm() {7 const [timestamp, setTimestamp] = useState("");89 useEffect(() => {10 setTimestamp(Date.now().toString());11 }, []);1213 return (14 <form action="/api/submit" method="POST">15 <input type="hidden" name="timestamp" value={timestamp} />16 <input type="text" name="data" />17 <button type="submit">Submit</button>18 </form>19 );20}2122// Server-side validation23// src/app/api/submit/route.ts2425import { NextRequest, NextResponse } from "next/server";2627const MIN_SUBMIT_TIME = 2000; // 2 seconds minimum28const MAX_SUBMIT_TIME = 3600000; // 1 hour maximum2930export async function POST(request: NextRequest) {31 const body = await request.json();3233 const timestamp = parseInt(body.timestamp);34 const now = Date.now();35 const elapsed = now - timestamp;3637 // Too fast = bot38 if (elapsed < MIN_SUBMIT_TIME) {39 console.log(`Bot detected: submitted in ${elapsed}ms`);40 return NextResponse.json(41 { error: "Please take your time filling the form" },42 { status: 400 }43 );44 }4546 // Too slow = stale token47 if (elapsed > MAX_SUBMIT_TIME) {48 return NextResponse.json(49 { error: "Form expired. Please refresh and try again" },50 { status: 400 }51 );52 }5354 // Process legitimate submission55}
Detect suspicious user agents
1// src/lib/bot-detection.ts23const BOT_PATTERNS = [4 /bot/i,5 /crawler/i,6 /spider/i,7 /scraper/i,8 /curl/i,9 /wget/i,10 /python-requests/i,11 /axios/i,12 /node-fetch/i,13 /headless/i,14 /phantom/i,15 /selenium/i,16 /puppeteer/i,17];1819// Known good bots (search engines)20const GOOD_BOTS = [21 /googlebot/i,22 /bingbot/i,23 /yandexbot/i,24 /duckduckbot/i,25 /slurp/i, // Yahoo26];2728export function analyzeUserAgent(userAgent: string | null): {29 isBot: boolean;30 isGoodBot: boolean;31 confidence: number;32} {33 if (!userAgent) {34 return { isBot: true, isGoodBot: false, confidence: 0.9 };35 }3637 // Check for good bots first38 for (const pattern of GOOD_BOTS) {39 if (pattern.test(userAgent)) {40 return { isBot: true, isGoodBot: true, confidence: 0.95 };41 }42 }4344 // Check for bad bot patterns45 for (const pattern of BOT_PATTERNS) {46 if (pattern.test(userAgent)) {47 return { isBot: true, isGoodBot: false, confidence: 0.8 };48 }49 }5051 // Suspicious: no version numbers52 if (!/\d/.test(userAgent)) {53 return { isBot: true, isGoodBot: false, confidence: 0.6 };54 }5556 return { isBot: false, isGoodBot: false, confidence: 0.1 };57}5859// Usage in API route60export async function POST(request: NextRequest) {61 const userAgent = request.headers.get("user-agent");62 const { isBot, isGoodBot, confidence } = analyzeUserAgent(userAgent);6364 if (isBot && !isGoodBot && confidence > 0.7) {65 return NextResponse.json(66 { error: "Access denied" },67 { status: 403 }68 );69 }7071 // Continue processing72}
Add bot detection at the network edge
1// src/middleware.ts23import { NextRequest, NextResponse } from "next/server";45// Protected paths6const PROTECTED_PATHS = [7 "/api/contact",8 "/api/signup",9 "/api/login",10];1112export function middleware(request: NextRequest) {13 const { pathname } = request.nextUrl;1415 // Only check protected paths16 if (!PROTECTED_PATHS.some((path) => pathname.startsWith(path))) {17 return NextResponse.next();18 }1920 // Check for missing headers that browsers always send21 const userAgent = request.headers.get("user-agent");22 const acceptLanguage = request.headers.get("accept-language");23 const acceptEncoding = request.headers.get("accept-encoding");2425 if (!userAgent || !acceptLanguage || !acceptEncoding) {26 return NextResponse.json(27 { error: "Access denied" },28 { status: 403 }29 );30 }3132 // Check for suspicious patterns33 const suspiciousHeaders = [34 request.headers.get("x-forwarded-for")?.split(",").length > 5,35 /python|curl|wget/i.test(userAgent),36 ];3738 if (suspiciousHeaders.some(Boolean)) {39 console.log("Suspicious request blocked:", {40 ip: request.ip,41 userAgent,42 path: pathname,43 });4445 return NextResponse.json(46 { error: "Access denied" },47 { status: 403 }48 );49 }5051 return NextResponse.next();52}5354export const config = {55 matcher: ["/api/:path*"],56};
Add Cloudflare Turnstile for strong bot protection
1// npm install @marsidev/react-turnstile23"use client";45import { Turnstile } from "@marsidev/react-turnstile";6import { useState } from "react";78export function ProtectedForm() {9 const [token, setToken] = useState<string>("");1011 return (12 <form action="/api/submit" method="POST">13 <input type="text" name="data" />1415 <Turnstile16 siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}17 onSuccess={(token) => setToken(token)}18 />1920 <input type="hidden" name="turnstileToken" value={token} />2122 <button type="submit" disabled={!token}>23 Submit24 </button>25 </form>26 );27}2829// Server-side verification30// src/app/api/submit/route.ts3132async function verifyTurnstile(token: string): Promise<boolean> {33 const response = await fetch(34 "https://challenges.cloudflare.com/turnstile/v0/siteverify",35 {36 method: "POST",37 headers: { "Content-Type": "application/json" },38 body: JSON.stringify({39 secret: process.env.TURNSTILE_SECRET_KEY,40 response: token,41 }),42 }43 );4445 const data = await response.json();46 return data.success;47}4849export async function POST(request: NextRequest) {50 const body = await request.json();5152 const isValid = await verifyTurnstile(body.turnstileToken);5354 if (!isValid) {55 return NextResponse.json(56 { error: "CAPTCHA verification failed" },57 { status: 400 }58 );59 }6061 // Process submission62}