أربع خطوات للدمج
الواجهة الأمامية تبدأ الدخول
أنشئ نسخة عبر createTutuId({ baseUrl, clientId }) من @tutu/id-sdk واستدعِ idSdk.startLogin() عند النقر. تُولّد SDK زوج PKCE وتحوّل إلى صفحة تفويض TutuId.
المستخدم يُصرّح في TutuId
في شاشة الموافقة /oauth/authorize ينقر المستخدم «سماح» (تُتخطّى تلقائيًا إن سبق التفويض). بعد تحقّق TutuId من client_id وقائمة redirect_uri المسموح بها، يعيد التوجيه إلى /auth/callback لديك مع رمز تفويض.
صفحة الاستدعاء تجلب الـ code
في /auth/callback استدعِ idSdk.completeCallback() للحصول على { code, codeVerifier }. سلّم هاتين القيمتين إلى خادمك الخلفي، ولا تبادل الـ token مباشرة في المتصفح.
الخادم الخلفي يبادل الـ token ← ينشئ الحساب
يبادل خادمك code + codeVerifier بـ access_token، ويستدعي /userinfo للحصول على { sub, email, emailVerified, ... }، ثم يربط أو ينشئ حسابًا محليًا حسب sub، ويُصدر جلستك الخاصة.
★ الحماية من الاستيلاء على الحساب (إلزامي)
خادمك الخلفي لا يجوز له ربط أو إنشاء حساب محلي إلا حسب sub (المعرّف الفريد للحساب). وفقط عندما emailVerified === true يمكنك ربط حساب محلي قائم بالبريد؛ وإلا فأنشئ حسابًا مستقلًا واترك البريد فارغًا.
السبب: قد يسجّل مهاجم حساب Tutu مسبقًا ببريد الضحية؛ والدمج ببريد غير مُتحقَّق يسلّم حساب الضحية المحلي. هذا خط أحمر بمستوى CVE.
★ فرِّع تسجيل الخروج: للحسابات المسجَّلة محليًا (غير المرتبطة بـ Tutu ID) لا تستدعِ logoutEverywhere() — فذلك ينهي جلسة Tutu ID المركزية غير ذات الصلة في المتصفح ويُخرج المستخدم من جميع المنتجات. هو أراد الخروج من منتجك فقط. لا تُسلسل إلا عند ارتباط الحساب فعليًا.
مرجع واجهة SDK
@tutu/id-sdk · من جانب العميل بالكامل، بلا اعتماديات (يتطلب Web Crypto + sessionStorage).
createTutuId({ baseUrl, clientId, redirectUri? }) ينشئ نسخة SDK. القيمة الافتراضية لـ redirectUri هي ${location.origin}/auth/callback وتتكيّف مع dev/prod.
startLogin(scope?) يُولّد زوج PKCE ويحوّل إلى صفحة التفويض، بادئًا «الدخول عبر Tutu».
trySilentLogin(scope?) الدخول الصامت: إن وُجدت جلسة مركزية وتفويض سابق يبادل الرمز بسلاسة؛ وإلا يعود فورًا إلى حالة الخروج (دون مقاطعة المستخدم).
completeCallback() يحلّل النتيجة في صفحة الاستدعاء: عند النجاح يعيد { code, codeVerifier }، وعند الفشل/الإلغاء يعيد { error }.
logoutEverywhere(returnTo?) يُنهي الجلسة المركزية ويسجّل الخروج بالتتابع من كل منتجات الحزمة، ثم يعود إلى returnTo.
تريد رؤيته عمليًا أولًا؟