الروابط السحرية
مصادقة بدون كلمة مرور باستخدام الروابط السحرية عبر البريد الإلكتروني.
ملاحظة: هذا محتوى تجريبي/مؤقت لأغراض العرض التوضيحي.
توفر الروابط السحرية مصادقة بدون كلمة مرور عبر إرسال رابط يُستخدم لمرة واحدة إلى البريد الإلكتروني للمستخدم.
آلية العمل
- يُدخل المستخدم عنوان بريده الإلكتروني
- يُرسل النظام رسالة بريد إلكتروني تحتوي على رابط فريد
- ينقر المستخدم على الرابط الموجود في بريده الإلكتروني
- يُسجَّل دخول المستخدم تلقائيًا
المزايا
- لا كلمة مرور لتذكّرها - تجربة مستخدم أفضل
- أمان أعلى - لا توجد كلمة مرور يمكن سرقتها
- عوائق أقل - عملية إنشاء حساب أسرع
- التحقق من البريد الإلكتروني - يؤكد ملكية البريد الإلكتروني
التنفيذ
نموذج الرابط السحري
'use client';
import { useForm } from 'react-hook-form';
import { sendMagicLinkAction } from '../_lib/actions';
export function MagicLinkForm() {
const { register, handleSubmit, formState: { isSubmitting } } = useForm();
const [sent, setSent] = useState(false);
const onSubmit = async (data) => {
const result = await sendMagicLinkAction(data);
if (result.success) {
setSent(true);
}
};
if (sent) {
return (
<div className="text-center">
<h2>Check your email</h2>
<p>We've sent you a magic link to sign in.</p>
</div>
);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Email address</label>
<input
type="email"
{...register('email', { required: true })}
placeholder="you@example.com"
/>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Sending...' : 'Send magic link'}
</button>
</form>
);
}
إجراء الخادم
'use server';
import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { z } from 'zod';
export const sendMagicLinkAction = enhanceAction(
async (data) => {
const client = getSupabaseServerClient();
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
const { error } = await client.auth.signInWithOtp({
email: data.email,
options: {
emailRedirectTo: `${origin}/auth/callback`,
shouldCreateUser: true,
},
});
if (error) throw error;
return {
success: true,
message: 'Check your email for the magic link',
};
},
{
schema: z.object({
email: z.string().email(),
}),
}
);
الإعداد
التفعيل في Supabase
- انتقل إلى Authentication ← Providers ← Email
- فعّل خيار «Enable Email Provider»
- فعّل خيار «Enable Email Confirmations»
إعداد قالب البريد الإلكتروني
خصّص رسالة الرابط السحري في لوحة تحكم Supabase:
- انتقل إلى Authentication ← Email Templates
- اختر «Magic Link»
- خصّص القالب:
<h2>Sign in to {{ .SiteURL }}</h2>
<p>Click the link below to sign in:</p>
<p><a href="{{ .ConfirmationURL }}">Sign in</a></p>
<p>This link expires in {{ .TokenExpiryHours }} hours.</p>
معالج الاستدعاء العكسي
تعامل مع الاستدعاء العكسي للرابط السحري:
// app/auth/callback/route.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const requestUrl = new URL(request.url);
const token_hash = requestUrl.searchParams.get('token_hash');
const type = requestUrl.searchParams.get('type');
if (token_hash && type === 'magiclink') {
const cookieStore = cookies();
const supabase = createRouteHandlerClient({ cookies: () => cookieStore });
const { error } = await supabase.auth.verifyOtp({
token_hash,
type: 'magiclink',
});
if (!error) {
return NextResponse.redirect(new URL('/home', request.url));
}
}
// إرجاع خطأ في حال فشل التحقق
return NextResponse.redirect(
new URL('/auth/sign-in?error=invalid_link', request.url)
);
}
الميزات المتقدمة
إعادة توجيه مخصصة
حدّد الوجهة التي ينتقل إليها المستخدمون بعد النقر على الرابط:
await client.auth.signInWithOtp({
email: data.email,
options: {
emailRedirectTo: `${origin}/onboarding`,
},
});
تعطيل إنشاء الحساب التلقائي
اشترط أن ينشئ المستخدمون حساباتهم أولًا:
await client.auth.signInWithOtp({
email: data.email,
options: {
shouldCreateUser: false, // عدم إنشاء مستخدمين جدد
},
});
انتهاء صلاحية الرمز
اضبط مدة صلاحية الرابط (الافتراضية: ساعة واحدة):
-- في محرر SQL الخاص بـ Supabase ALTER TABLE auth.users SET default_token_lifetime = '15 minutes';
تحديد معدل الطلبات
امنع إساءة الاستخدام عبر تحديد معدل طلبات الروابط السحرية:
import { ratelimit } from '~/lib/rate-limit';
export const sendMagicLinkAction = enhanceAction(
async (data, user, request) => {
// تحديد المعدل حسب عنوان IP
const ip = request.headers.get('x-forwarded-for') || 'unknown';
const { success } = await ratelimit.limit(ip);
if (!success) {
throw new Error('Too many requests. Please try again later.');
}
const client = getSupabaseServerClient();
await client.auth.signInWithOtp({
email: data.email,
});
return { success: true };
},
{ schema: EmailSchema }
);
اعتبارات أمنية
انتهاء صلاحية الرابط
يجب أن تنتهي صلاحية الروابط السحرية بسرعة:
- الافتراضي: ساعة واحدة
- الموصى به: من 15 إلى 30 دقيقة في بيئة الإنتاج
- مدة أقصر للإجراءات الحساسة
الاستخدام لمرة واحدة
يجب إبطال الروابط بعد استخدامها:
// تتولى Supabase ذلك تلقائيًا // لا يمكن استخدام كل رابط إلا مرة واحدة
التحقق من البريد الإلكتروني
تأكد من التحقق من عناوين البريد الإلكتروني:
const { data: { user } } = await client.auth.getUser();
if (!user.email_confirmed_at) {
redirect('/verify-email');
}
تجربة المستخدم
حالة التحميل
اعرض ملاحظات للمستخدم أثناء الإرسال:
export function MagicLinkForm() {
const [status, setStatus] = useState<'idle' | 'sending' | 'sent'>('idle');
const onSubmit = async (data) => {
setStatus('sending');
await sendMagicLinkAction(data);
setStatus('sent');
};
return (
<>
{status === 'idle' && <EmailForm onSubmit={onSubmit} />}
{status === 'sending' && <SendingMessage />}
{status === 'sent' && <CheckEmailMessage />}
</>
);
}
إعادة إرسال الرابط
اسمح للمستخدمين بطلب رابط جديد:
export function ResendMagicLink({ email }: { email: string }) {
const [canResend, setCanResend] = useState(false);
const [countdown, setCountdown] = useState(60);
useEffect(() => {
if (countdown > 0) {
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
return () => clearTimeout(timer);
} else {
setCanResend(true);
}
}, [countdown]);
const handleResend = async () => {
await sendMagicLinkAction({ email });
setCountdown(60);
setCanResend(false);
};
return (
<button onClick={handleResend} disabled={!canResend}>
{canResend ? 'Resend link' : `Resend in ${countdown}s`}
</button>
);
}
قابلية تسليم البريد الإلكتروني
SPF وDKIM وDMARC
اضبط مصادقة البريد الإلكتروني:
- أضف سجل SPF إلى DNS
- فعّل توقيع DKIM
- أعدّ سياسة DMARC
نطاق بريد إلكتروني مخصص
استخدم نطاقك الخاص لتحسين قابلية التسليم:
- انتقل إلى Project Settings ← Auth
- اضبط خادم SMTP مخصصًا
- تحقق من ملكية النطاق
مراقبة الرسائل المرتدة
تتبّع مشكلات تسليم البريد الإلكتروني:
// التعامل مع رسائل البريد الإلكتروني المرتدة
export async function handleEmailBounce(email: string) {
await client.from('email_bounces').insert({
email,
bounced_at: new Date(),
});
// إخطار المستخدم عبر قناة أخرى
}
الاختبار
التطوير المحلي
في بيئة التطوير، تُرسل رسائل البريد الإلكتروني إلى InBucket:
http://localhost:54324
راجع هذا العنوان للاطلاع على رسائل الروابط السحرية أثناء الاختبار.
وضع الاختبار
أنشئ رابطًا تجريبيًا دون إرسال بريد إلكتروني:
if (process.env.NODE_ENV === 'development') {
console.log('Magic link URL:', confirmationUrl);
}
أفضل الممارسات
- تواصل واضح - اطلب من المستخدمين التحقق من مجلد الرسائل غير المرغوب فيها
- مدة صلاحية قصيرة - من 15 إلى 30 دقيقة لدواعٍ أمنية
- تحديد معدل الطلبات - لمنع إساءة الاستخدام
- خيار بديل - وفّر المصادقة بكلمة المرور كخيار احتياطي
- نطاق مخصص - لتحسين قابلية التسليم
- مراقبة التسليم - تتبّع الرسائل المرتدة وحالات الفشل
- خيار إعادة الإرسال - اسمح للمستخدمين بطلب رابط جديد
- ملاءمة الأجهزة المحمولة - تأكد من أن الروابط تعمل على الأجهزة المحمولة