كيف أوقفت السبام في نماذج التواصل دون إزعاج المستخدمين الحقيقيين
ألغاز reCAPTCHA كانت تكلّفني عملاء حقيقيين، وحقل الـ honeypot وحده لم يكن كافيًا. هذا إعداد Cloudflare Turnstile الذي أستخدمه الآن في مواقع Nuxt، بما فيه التحقق من جهة السيرفر الذي ينساه كثيرون.
كل نموذج تواصل وضعته على الإنترنت وجدته البوتات خلال أيام. قبل بضع سنوات كان نموذج أحد العملاء يستقبل أكثر من مئة رسالة سبام يوميًا: عروض عملات رقمية، و"تدقيق SEO" مجاني، ورسائل بلغات لا يتحدثها أحد في الفريق. والاستفسارات الحقيقية مدفونة في مكان ما بينها.
جرّبت الخيارات المعتادة قبل أن أستقر على ما أستخدمه الآن.
ما جرّبته أولًا
reCAPTCHA v2 (مربع الاختيار وإشارات المرور). أوقف السبام، لكن الناس يكرهونه. أخبرني أحد العملاء أن زبونًا اتصل به فقط ليقول إنه استسلم بعد الجولة الثالثة من "اختر كل الحافلات". هذا عميل ضائع، ولن تعرف أبدًا كم غيره ضاع.
حقل honeypot. حقل مخفي لا يراه البشر وتملؤه البوتات. رخيص وغير مرئي، وما زال يلتقط عددًا مفاجئًا من البوتات البسيطة. لكن البوتات الأذكى تتجاوزه. ما زلت أستخدمه، لكن ليس وحده.
تحديد عدد الطلبات حسب IP. مفيد ضد الإغراق، وعديم الفائدة ضد سبام يأتي من عناوين IP كثيرة مختلفة.
Cloudflare Turnstile
Turnstile هو بديل Cloudflare للـ CAPTCHA. أغلب الزوار لا يرون أي تحدٍّ أصلًا. يجري بعض الفحوصات في الخلفية ويعطي المتصفح token. وإذا لم يكن متأكدًا، يعرض مربع اختيار واحدًا. بلا إشارات مرور.
مجاني، ولا يحتاج أن يكون موقعك خلف Cloudflare، وهو ما أستخدمه في هذا الموقع وفي مشاريع عملاء مثل AZ Containers.
الجزء الذي يخطئ فيه الناس
إضافة الـ widget إلى الصفحة هي النصف السهل. كثير من الشروحات تتوقف هنا، وهذا يعني أن النموذج "محمي" بـ widget يستطيع البوت ببساطة تجاهله وإرسال الطلب مباشرة إلى الـ API.
يجب أن تتحقق من الـ token على السيرفر. في Nuxt، الـ API route عندي يفعل شيئًا كهذا:
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const { turnstileSecret } = useRuntimeConfig()
const verification = await $fetch<{ success: boolean }>(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
body: new URLSearchParams({
secret: turnstileSecret,
response: body.turnstileToken,
}),
},
)
if (!verification.success) {
throw createError({ statusCode: 400, message: 'Captcha verification failed' })
}
// only now send the email
})
بعض التفاصيل:
- المفتاح السري يُحفظ في
runtimeConfig(على السيرفر فقط)، ويُحمَّل من متغير بيئة. لا تضعه أبدًا فيpublic. - الـ tokens تُستخدم مرة واحدة وتنتهي صلاحيتها بعد دقائق. إذا أخذ المستخدم وقتًا طويلًا في النموذج أو فشل الإرسال، أعد ضبط الـ widget حتى يحصل على token جديد.
- تحقّق من باقي الحقول على السيرفر أيضًا. Turnstile يخبرك أن المرسل غالبًا إنسان، لكنه لا يخبرك أن البريد الإلكتروني صحيح.
طريقتي الحالية في حماية النماذج
- حقل honeypot (يلتقط البوتات الكسولة مجانًا)
- Turnstile، مع التحقق على السيرفر
- التحقق من كل حقل على السيرفر
- تحديد بسيط لعدد الطلبات على الـ endpoint
منذ انتقلت إلى هذا الإعداد، انخفض السبام في النماذج التي أديرها إلى ما يقارب الصفر. والأهم أنني لم أتلقَّ رسالة واحدة من شخص حقيقي يقول "النموذج عندكم لا يعمل".
الأمان الذي يُحبط مستخدميك ليس أمانًا حقيقيًا. هو فقط ينقل الضرر إلى مكان لا تراه.