في أول يوم لموظفة جديدة في قسم المالية تحتاج إلى حساب في البريد، وفي سيرفر Git، وفي قاعدة المعرفة Wiki، وفي تطبيق المحادثة Chat، وفي نظام التذاكر، وربما في خمسة أنظمة أخرى، وفي آخر يوم لها يجب أن تغلق كل هذه الحسابات في الساعة نفسها. وإذا كنت قد ربطت تطبيقاتك بمزود هوية Identity Provider مثل authentik فقد حللت نصف المشكلة فقط، لأن الدخول الموحد Single Sign-On واختصاراً SSO يجيب عن سؤال «من الذي يسجل دخوله الآن؟»، ولكنه لا يجيب عن سؤال «ما الحسابات التي يجب أن توجد داخل كل تطبيق، وبأي صلاحيات، ومتى تغلق؟».
وهذا النصف الثاني له اسم هو تجهيز الحسابات Provisioning وإلغاؤها Deprovisioning، وله معيار مفتوح اسمه SCIM، وهو موضوع هذا المقال. وإذا لم تكن تعرف بعد كيف يعمل الدخول الموحد نفسه فابدأ بشرح OAuth وOpenID Connect للمبتدئين، لأننا هنا نبني عليه ولا نكرره، وسوف نناقش في هذا المقال ما يلي:
- الفجوة التي يتركها SSO، والحلول الأولى التي تخطر على البال: الإنشاء اليدوي، والإنشاء التلقائي عند الدخول JIT، والسكربتات، ولماذا يفشل كل منها.
- ما هو SCIM، ومن يرسل الطلبات ومن يستقبلها، والمصطلحات التي سوف تقابلها في كل أداة.
- دورة حياة الحساب كاملة مع SCIM: التعيين والنقل والمغادرة.
- تجربة عملية على سيرفر SCIM حقيقي بأوامر
curlومخرجاتها كما هي، ثم ما يرسله authentik فعلاً عند التعيين والتعطيل وسحب الصلاحية. - من يدعم SCIM من مزودي الهوية والتطبيقات مفتوحة المصدر، وما البديل عندما لا يدعمه التطبيق.
- الأمان والأخطاء الشائعة، ثم الصورة الكاملة: SSO مع SCIM مع المصادقة متعددة العوامل MFA.
ما الذي لا يحله الدخول الموحد SSO؟
عندما يدخل الموظف إلى Gitea عبر authentik فإن Gitea لا يحفظ كلمة مروره، ولكنه يحفظ حساباً كاملاً باسمه: مستودعاته Repositories، وعضويته في الفرق، ومفاتيح SSH التي أضافها، والرموز Access Tokens التي أنشأها لعمليات git عبر HTTPS. والأمر نفسه في كل تطبيق آخر، فالحساب يعيش داخل التطبيق، والدخول الموحد مجرد طريقة للوصول إليه. لذلك يبقى عليك أن تجيب عن ثلاثة أسئلة لا يجيب عنها SSO:
- التعيين Joiner: الموظف الجديد يحتاج إلى حساباته من أول يوم، ومنها حسابات في تطبيقات لن يفتحها بنفسه قبل أن يرسل إليه زميله رابطاً أو يضيفه إلى قناة، فإذا لم يكن الحساب موجوداً فلا أحد يستطيع أن يشاركه ملفاً أو يضيفه إلى مجموعة.
- النقل Mover: انتقل الموظف من المالية إلى التطوير، فيجب أن يخرج من مجموعات المالية في كل تطبيق ويدخل مجموعات التطوير، وإذا لم يحدث ذلك فسوف يحمل معه صلاحيات قسمه القديم سنوات، وهذا ما يسمى تراكم الصلاحيات Privilege Creep.
- المغادرة Leaver: عند مغادرته تعطل حسابه في مزود الهوية فلا يستطيع الدخول عبر الويب، ولكن حساباته داخل التطبيقات تبقى نشطة، ومعها كل باب لا يمر على مزود الهوية أصلاً: مفاتيح SSH، والرموز Tokens، وروابط المشاركة Share Links، وحسابات البوتات Bots التي أنشأها.
والصورة التالية تبين هذه الفجوة: المدير يعطل الحساب في مزود الهوية فيرفض الدخول الموحد، ولكن لا تصل أي رسالة إلى التطبيقات، فتبقى الحسابات فيها نشطة، والرمز المحفوظ على حاسوب الموظفة السابقة ما زال يعمل:

وقد ذكرنا هذه النقطة بالتفصيل في ربط Gitea مع authentik، حيث تبقى مفاتيح SSH والرموز صالحة بعد تعطيل الحساب في authentik لأنها لا تمر عليه، وفي دليل استضافة البريد للمؤسسات حيث يبقى صندوق بريد الموظف المغادر يستقبل الرسائل. والسؤال الآن: كيف تصل الحسابات إلى التطبيقات وكيف تغلق؟ وفيما يلي الحلول التي يجربها أغلب الفرق بالترتيب.
الحل الأول: قائمة مهام يدوية
أبسط حل هو قائمة تحقق Checklist يمر عليها مسؤول الأنظمة عند كل تعيين ومغادرة: ينشئ الحساب في كل تطبيق ويضيفه إلى مجموعاته، ثم يعطله في كل تطبيق عند المغادرة. وهذا الحل يعمل مع خمسة موظفين وثلاثة تطبيقات، ولكنه يفشل مع النمو، والسبب أن كل خطوة فيه تعتمد على ذاكرة شخص: تطبيق أضيف الشهر الماضي ولم يدخل القائمة، وطلب مغادرة وصل يوم الخميس فنفذ يوم الأحد، ومسؤول في إجازة. والأسوأ أنك لا تملك طريقة تتأكد بها أن الحسابات في التطبيقات تطابق الواقع، فلا أحد يعرف كم حساباً نشطاً لموظفين غادروا قبل سنة.
الحل الثاني: الإنشاء التلقائي عند أول دخول JIT
أغلب التطبيقات التي تدعم OIDC أو SAML تستطيع أن تنشئ الحساب تلقائياً عند أول دخول عبر مزود الهوية، وهذا ما يسمى الإنشاء في الوقت المناسب Just-in-Time Provisioning واختصاراً JIT، وقد فعلناه في Gitea عبر خيار التسجيل التلقائي Auto Registration. وهذا الحل يحل مشكلة التعيين جزئياً، ولكن لاحظ ما لا يفعله:
- لا ينشئ الحساب قبل الدخول: الحساب غير موجود حتى يدخل صاحبه بنفسه، فلا تستطيع أن تضيفه إلى فريق أو تشاركه مستنداً قبل ذلك، وفي البريد يعني ذلك أن الرسائل إليه ترفض، كما يوضح توثيق Stalwart في حالة OIDC.
- التغيير لا يصل إلا عند الدخول التالي: إذا سحبت من الموظف مجموعة
gitea-adminsفي authentik فإن Gitea يسحب صلاحية المدير عند دخوله التالي فقط، والجلسة المفتوحة تبقى بصلاحيتها القديمة. - لا يحذف شيئاً ولا يعطله: وهذه أخطر نقطة، فمن لا يدخل لا يرسل شيئاً، والموظف الذي غادر لن يدخل مرة أخرى عبر مزود الهوية، فيبقى حسابه في التطبيق كما هو. وقد نص على ذلك صراحة توثيق Stalwart، ويتتبع فريق Gitea المشكلة نفسها في قائمة متطلبات الدخول الموحد حيث يذكر أن الحساب الذي يحذف من OIDC أو LDAP يبقى في Gitea.
الحل الثالث: سكربت لكل تطبيق
بعد أن يتعب الفريق من القائمة اليدوية يكتب سكربتاً Script يستدعي واجهة برمجة كل تطبيق API: سكربت لإنشاء مستخدم في Gitea، وآخر لتعطيله في Mattermost، وثالث لإضافته إلى مجموعة في Outline. وهذا أفضل من العمل اليدوي، ولكنه يتحول بسرعة إلى مشروع صيانة، فلكل تطبيق واجهة مختلفة وأسماء حقول مختلفة وطريقة مصادقة مختلفة، وكل تحديث للتطبيق قد يكسر السكربت دون أن يلاحظ أحد، وعشرة تطبيقات تعني عشرة سكربتات يكتبها ويصلحها فريقك وحده.
وقد يتساءل البعض: لماذا لا يتفق الجميع على واجهة واحدة، فيكتب مزود الهوية الكود مرة واحدة ويكتب كل تطبيق الطرف الآخر مرة واحدة؟ والإجابة أن هذه الواجهة موجودة منذ 2015، وهي SCIM.
ما هو SCIM؟
SCIM اختصار System for Cross-domain Identity Management، أي نظام إدارة الهويات بين الأنظمة المختلفة، وهو معيار مفتوح من IETF يحدد واجهة REST تتبادل بيانات JSON، يستخدمها نظام يملك قائمة المستخدمين ليطلب من نظام آخر أن ينشئ مستخدماً أو يعدله أو يعطله أو يحذفه، ومثل ذلك للمجموعات Groups. والمعيار في إصداره الثاني SCIM 2.0 ثلاث وثائق صدرت في سبتمبر 2015:
- RFC 7642: المفاهيم وحالات الاستخدام، وهي وثيقة معلوماتية Informational تشرح السيناريوهات ومنها التعيين والنقل والمغادرة Joiner, Mover, Leaver.
- RFC 7643: المخطط Schema، أي شكل المستخدم والمجموعة وأسماء حقولهما وقواعد كل حقل.
- RFC 7644: البروتوكول Protocol، أي العناوين Endpoints والطلبات والردود والأخطاء والبحث والتعديل الجزئي.
والجملة التي تلخص الفكرة كلها موجودة في RFC 7642 نفسه، حيث يذكر أن SCIM يقوم بتجهيز الحسابات وإلغائها في سياق منفصل عن المصادقة Authentication، بخلاف الإنشاء عند الدخول. وهذا يعني أن مزود الهوية لا ينتظر أن يدخل المستخدم، وإنما يرسل التغيير بنفسه إلى كل تطبيق في اللحظة التي يحدث فيها.
ولو أردنا تشبيهاً من الحياة فالدخول الموحد هو بوابة المبنى التي تتحقق من بطاقتك عند الدخول، أما SCIM فهو التعميم الذي ترسله الموارد البشرية إلى كل قسم: «انضمت سارة إلى المالية، جهزوا مكتبها»، ثم «انتقلت إلى التطوير»، ثم «انتهت خدمتها، استلموا مفاتيحها». والبوابة وحدها لا تسترجع مفتاح الخزانة الذي بقي في جيب الموظفة.

المكونات والمصطلحات التي سوف تقابلها
سوف تجد المصطلحات التالية في توثيق كل مزود هوية وكل تطبيق يدعم SCIM، لذلك نمر عليها مرة واحدة بالترتيب الذي تحتاجه:
- كلاينت SCIM Client: الطرف الذي يملك قائمة المستخدمين ويرسل الطلبات، وهو غالباً مزود الهوية مثل authentik أو Microsoft Entra ID أو Okta. ويسميه المعيار أيضاً كلاينت التجهيز Provisioning Client.
- سيرفر SCIM أو مزود الخدمة Service Provider: التطبيق الذي يستقبل الطلبات وينفذها على حساباته، مثل GitLab أو Zulip. ولاحظ أن الاتجاه هنا معكوس عما تعودت عليه في SSO، فالتطبيق هنا هو السيرفر ومزود الهوية هو الكلاينت.
- العنوان الأساسي Base URL: العنوان الذي تبدأ منه كل الطلبات، مثل
https://chat.example.com/scim/v2، ويعطيك إياه التطبيق في إعداداته مع الرمز. - المورد Resource ونوع المورد Resource Type: المستخدم User والمجموعة Group هما المورد الأساسيان، ولكل منهما عنوان:
/Usersو/Groups، وتطبق عليهما عمليات الإنشاء والقراءة والتعديل والحذف المعروفة بـ CRUD عبرPOSTوGETوPUTوPATCHوDELETE. - عناوين الاكتشاف Discovery Endpoints: ثلاثة عناوين للقراءة فقط يسأل منها الكلاينت عما يدعمه التطبيق قبل أن يرسل أي طلب:
/ServiceProviderConfigيقول هل يدعم التعديل الجزئي PATCH والبحث Filter والطلبات المجمعة Bulk وما طريقة المصادقة، و/ResourceTypesيسرد أنواع الموارد وامتداداتها، و/Schemasيصف كل حقل وقواعده. - المعرف
id: معرف يضعه التطبيق نفسه عند الإنشاء، وينص RFC 7643 على أنه ثابت لا يتغير ولا يعاد استخدامه، وكل الطلبات اللاحقة على هذا المستخدم تستخدمه في العنوان، مثل/Users/6392bdbc…. - المعرف الخارجي
externalId: معرف يرسله الكلاينت من جهته، أي معرف المستخدم في مزود الهوية، حتى يستطيع أن يبحث عنه لاحقاً دون أن يحفظ جدولاً يربط معرفاته بمعرفات التطبيق. - الحقول الأساسية Core Attributes:
userNameوهو الاسم الفريد للدخول، وnameبأجزائه، وdisplayName، وemails، وactiveوهو حقل منطقي Boolean يعني أن الحساب يستطيع الدخول عندما تكون قيمتهtrueوأنه موقوف Suspended عندما تكونfalse. - أعضاء المجموعة
members: قائمة بمعرفات المستخدمينidداخل المجموعة، فالمجموعة تشير إلى المستخدم بمعرفه في التطبيق وليس باسمه. - امتداد المؤسسة Enterprise Extension: مخطط إضافي اسمه
urn:ietf:params:scim:schemas:extension:enterprise:2.0:Userيضيف حقولاً مثلemployeeNumberوdepartmentوmanager، ويستطيع أي تطبيق أن يعرف امتداده الخاص بالطريقة نفسها. - التعديل الجزئي PATCH: طلب يحمل المخطط
urn:ietf:params:scim:api:messages:2.0:PatchOpوقائمة عمليات Operations، كل عملية فيهاopمن ثلاث قيم هيaddوremoveوreplace، ومسارpathوقيمةvalue. وفي المقابل يستبدلPUTالمورد كاملاً. - البحث Filtering: بصيغة مثل
filter=userName eq "sara"، والمعاملات Operators هيeqوneوcoوswوewوprوgtوgeوltوleمعandوorوnot. - المصادقة برمز Bearer Token: المعيار لا يفرض طريقة مصادقة، ولكن RFC 7644 يذكر رموز Bearer مع TLS، وهي الطريقة الشائعة عملياً: يولد التطبيق رمزاً طويلاً، وتضعه في مزود الهوية، فيرسله في الترويسة
Authorization: Bearer …مع كل طلب. - الدفع Push والسحب Pull: في الدفع يرسل الكلاينت التغيير إلى التطبيق، وفي السحب يطلب التطبيق البيانات، وRFC 7642 يصف الاثنين، ولكن ما تجده في الأدوات اليوم هو الدفع غالباً.
- المزامنة عند التغيير والمزامنة الدورية: مزود الهوية الجيد يرسل الطلب لحظة التغيير Event-driven، ثم يمر دورياً على كل الحسابات ليصلح ما فاته، فـ authentik مثلاً يرسل كل تغيير فوراً ويزامن كل مزودات SCIM مزامنة كاملة مرة كل ساعة.
والصورة التالية تجمع هذه المكونات داخل سيرفر SCIM واحد:

id من التطبيق وexternalId من مزود الهويةدورة حياة الحساب مع SCIM: من أول يوم إلى آخر يوم
لنأخذ الموظفة نفسها، سارة، ونتبعها في ثلاث مراحل، ولاحظ أن المدير في كل مرحلة يعمل في مكان واحد فقط هو مزود الهوية، والباقي يرسله SCIM:
- التعيين Joiner: ينشئ المدير حساب سارة في مزود الهوية ويضيفه إلى مجموعة
finance، فيرسل مزود الهوية إلى كل تطبيق مرتبط طلبPOST /UsersثمPATCH /Groups/…لإضافتها إلى المجموعة، فتجد سارة حساباتها جاهزة في أول يوم، ويستطيع زملاؤها أن يشاركوها الملفات قبل أن تدخل. - النقل Mover: بعد ثمانية أشهر تنتقل إلى التطوير، فيخرجها المدير من
financeويضيفها إلىengineering، فيرسل مزود الهوية طلبي PATCH على المجموعتين في كل تطبيق، وتختفي صلاحيات المالية في اللحظة نفسها وليس عند دخولها التالي. - المغادرة Leaver: في آخر يوم يعطل المدير حسابها، فيرسل مزود الهوية
active: falseإلى كل تطبيق، فيوقف كل تطبيق حسابها، ثم يأتي الحذف لاحقاً بحسب سياسة الاحتفاظ بالبيانات Retention Policy لديكم.

والجدول التالي يقارن ما يحدث في كل مرحلة بين العمل اليدوي والإنشاء عند الدخول وSCIM:
| المرحلة | يدوياً | JIT | SCIM |
|---|---|---|---|
| التعيين | بعد أن ينفذ أحدهم القائمة، وقد ينسى تطبيقاً | عند أول دخول للموظف، وليس قبله | قبل أول دخول، في كل تطبيق مرتبط |
| النقل | غالباً لا يحدث، فتتراكم الصلاحيات | عند الدخول التالي فقط | فور التغيير في مزود الهوية |
| المغادرة | بحسب ذاكرة المسؤول وسرعته | لا يحدث أبداً | تعطيل فوري في كل تطبيق، ومراجعة كاملة كل ساعة في authentik |
| هل تعرف الحالة الحقيقية؟ | لا، إلا بمراجعة كل تطبيق يدوياً | لا | نعم بقدر ما يدعم التطبيق، لأن مزود الهوية يقارن ما أرسله بما في التطبيق |
تجربة عملية: نرسل طلبات SCIM بأنفسنا
أفضل طريقة لفهم SCIM أن تكون أنت مزود الهوية لدقائق، فترسل الطلبات بنفسك وترى الردود. وسوف نستخدم scim2-server، وهو سيرفر SCIM مرجعي مفتوح المصدر مكتوب بلغة Python يطبق RFC 7643 وRFC 7644 ويحفظ البيانات في الذاكرة، وصمم أصلاً لاختبار كلاينت SCIM، وهذا بالضبط ما نحتاجه. والمثال التالي يشغله في Container على جهازك برمز ثابت:
docker run -d --name scim-test -p 127.0.0.1:18480:8080 \
ghcr.io/python-scim/scim2-server:0.6.0 --bearer-token lab-scim-token-7f3a
SCIM=http://127.0.0.1:18480/v2
TOKEN=lab-scim-token-7f3aلاحظ أن المنفذ مربوط بالعنوان 127.0.0.1 فقط، فلا يصل إليه أحد من الشبكة، وهذا سيرفر تجارب يحفظ كل شيء في الذاكرة ويفقده عند إيقافه. ونبدأ كما يبدأ أي مزود هوية، أي بسؤال التطبيق عما يدعمه:
curl -s $SCIM/ServiceProviderConfig -H "Authorization: Bearer $TOKEN"والمخرج سوف يكون كما يلي:
{"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"], "meta": {"resourceType": "ServiceProviderConfig", "location": "http://127.0.0.1:18480/v2/ServiceProviderConfig"}, "patch": {"supported": true}, "bulk": {"supported": true, "maxOperations": 1000, "maxPayloadSize": 1048576}, "filter": {"supported": true, "maxResults": 1000}, "changePassword": {"supported": true}, "sort": {"supported": true}, "etag": {"supported": true}, "authenticationSchemes": [{"type": "oauthbearertoken", "name": "bearer_token", "description": "HTTP Bearer Token", "specUri": "https://datatracker.ietf.org/doc/html/rfc6750"}]}وهذا الرد هو ما يقرأه مزود الهوية ليقرر كيف يتعامل مع التطبيق، فإذا كانت قيمة patch.supported هي false مثلاً فلن يرسل طلبات PATCH وإنما يستبدل المورد كاملاً. ولاحظ أن authentik نفسه يضبط ذلك تلقائياً في أوضاع التوافق Compatibility Modes لبعض الخدمات، فيعطل PATCH مع AWS مثلاً.
التعيين: إنشاء المستخدم
الآن نرسل ما يرسله مزود الهوية عند تعيين سارة، أي مستخدماً كاملاً مع امتداد المؤسسة ومعرفها في مزود الهوية externalId:
curl -s -i -X POST $SCIM/Users -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/scim+json" -d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],
"externalId": "8f14e45f-ceea-467a-9b7e-1c4d2e5a9a01",
"userName": "sara",
"name": {"givenName": "Sara", "familyName": "Ahmed"},
"displayName": "Sara Ahmed",
"emails": [{"value": "[email protected]", "type": "work", "primary": true}],
"active": true,
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {"employeeNumber": "1042", "department": "Finance"}
}'والمخرج سوف يكون كما يلي (بعد ترتيب ال JSON للقراءة):
HTTP/1.0 201 Created
Location: http://127.0.0.1:18480/v2/Users/6392bdbc87fc4a53bafa9e8b31967214
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"id": "6392bdbc87fc4a53bafa9e8b31967214",
"externalId": "8f14e45f-ceea-467a-9b7e-1c4d2e5a9a01",
"meta": {
"resourceType": "User",
"created": "2026-10-05T20:20:30.273231Z",
"lastModified": "2026-10-05T20:20:30.273231Z",
"location": "http://127.0.0.1:18480/v2/Users/6392bdbc87fc4a53bafa9e8b31967214",
"version": "W/\"1\""
},
"userName": "sara",
"name": {
"familyName": "Ahmed",
"givenName": "Sara"
},
"displayName": "Sara Ahmed",
"active": true,
"emails": [
{
"value": "[email protected]",
"type": "work",
"primary": true
}
],
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "1042",
"department": "Finance"
}
}في المخرج أعلاه لاحظ التالي:
- رمز الحالة
201 CreatedوالترويسةLocationتحمل عنوان المستخدم الجديد. - القيمة
idوضعها السيرفر، وهي ما يحفظه مزود الهوية ويستخدمه في كل طلب لاحق على سارة، فـ authentik مثلاً يحفظ هذا الربط بين المستخدم عنده ومعرفه في كل تطبيق. - القيمة
externalIdرجعت كما أرسلناها، وهي الجسر في الاتجاه الآخر، أي من التطبيق إلى مزود الهوية. - الحقل
meta.versionنسخة ETag يستطيع الكلاينت أن يرسلها مع التعديل حتى لا يكتب فوق تعديل لم يره.
ماذا يحدث إذا أرسل مزود الهوية الطلب نفسه مرة ثانية، بسبب انقطاع في الشبكة مثلاً؟
curl -s -X POST $SCIM/Users -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/scim+json" \
-d '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"userName":"sara"}'والمخرج سوف يكون كما يلي، مع رمز الحالة 409:
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:Error"
],
"status": "409",
"scimType": "uniqueness",
"detail": "One or more of the attribute values are already in use or are reserved"
}وهذا الخطأ متوقع ومفيد، فـ userName فريد، والكلاينت الجيد لا يتوقف عنده وإنما يبحث عن المستخدم الموجود ويربطه بنفسه. وهذا ما يفعله authentik فعلاً في كود مزود SCIM، فإذا رد التطبيق بأن المستخدم موجود وكان البحث مدعوماً أرسل الطلب التالي ليجده.
البحث عن مستخدم
curl -s -G $SCIM/Users -H "Authorization: Bearer $TOKEN" \
--data-urlencode 'filter=userName eq "sara"' \
--data-urlencode 'attributes=userName,active,emails'والمخرج سوف يكون كما يلي:
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
],
"totalResults": 1,
"startIndex": 1,
"itemsPerPage": 1,
"Resources": [
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"id": "6392bdbc87fc4a53bafa9e8b31967214",
"userName": "sara",
"active": true,
"emails": [
{
"value": "[email protected]",
"type": "work",
"primary": true
}
]
}
]
}لاحظ أن الرد قائمة ListResponse حتى لو كانت النتيجة واحدة، وأن المعامل attributes قصر الرد على الحقول التي طلبناها، وهذا مهم عندما يمر مزود الهوية على آلاف المستخدمين في المزامنة الدورية.
المجموعات: إضافة عضو بطلب PATCH
ننشئ مجموعة finance كما ينشئها مزود الهوية:
curl -s -X POST $SCIM/Groups -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/scim+json" \
-d '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:Group"],"externalId":"grp-finance","displayName":"finance","members":[]}'والمخرج سوف يكون كما يلي:
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:Group"
],
"id": "85a02be344cc4e3b951489c20330883c",
"externalId": "grp-finance",
"meta": {
"resourceType": "Group",
"created": "2026-10-05T20:20:45.554610Z",
"lastModified": "2026-10-05T20:20:45.554610Z",
"location": "http://127.0.0.1:18480/v2/Groups/85a02be344cc4e3b951489c20330883c",
"version": "W/\"3\""
},
"displayName": "finance",
"members": []
}ثم نضيف سارة إليها بطلب PATCH يحمل عملية add واحدة، ولاحظ أن العضو يضاف بمعرفه id في التطبيق وليس باسمه:
curl -s -o /dev/null -w "%{http_code}\n" -X PATCH $SCIM/Groups/85a02be344cc4e3b951489c20330883c \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/scim+json" -d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "add", "path": "members", "value": [{"value": "6392bdbc87fc4a53bafa9e8b31967214"}]}
]
}'والمخرج سوف يكون 204، أي نجح الطلب دون محتوى في الرد، وهذا مسموح في RFC 7644. وللتأكد نسأل عن المجموعات التي تضم سارة:
curl -s -G $SCIM/Groups -H "Authorization: Bearer $TOKEN" \
--data-urlencode 'filter=members.value eq "6392bdbc87fc4a53bafa9e8b31967214"' \
--data-urlencode 'attributes=displayName'والمخرج سوف يكون كما يلي:
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
],
"totalResults": 1,
"startIndex": 1,
"itemsPerPage": 1,
"Resources": [
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:Group"
],
"id": "85a02be344cc4e3b951489c20330883c",
"displayName": "finance"
}
]
}النقل: من المالية إلى التطوير
بعد أن أنشأنا مجموعة ثانية engineering بالطلب نفسه (ومعرفها 495a6144cc9c400e90873eaf85da1992)، ننقل سارة بثلاثة طلبات: إزالتها من المالية بعملية remove ومسار فيه بحث داخل القائمة، ثم إضافتها إلى التطوير، ثم تعديل قسمها في امتداد المؤسسة بعملية replace:
curl -s -o /dev/null -w "%{http_code}\n" -X PATCH $SCIM/Groups/85a02be344cc4e3b951489c20330883c \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/scim+json" -d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "remove", "path": "members[value eq \"6392bdbc87fc4a53bafa9e8b31967214\"]"}
]
}'
curl -s -o /dev/null -w "%{http_code}\n" -X PATCH $SCIM/Groups/495a6144cc9c400e90873eaf85da1992 \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/scim+json" -d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "add", "path": "members", "value": [{"value": "6392bdbc87fc4a53bafa9e8b31967214"}]}
]
}'
curl -s -o /dev/null -w "%{http_code}\n" -X PATCH $SCIM/Users/6392bdbc87fc4a53bafa9e8b31967214 \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/scim+json" -d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "replace", "path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department", "value": "Engineering"}
]
}'والمخرج سوف يكون كما يلي:
204
204
204ونتحقق من النتيجة بالبحث نفسه عن مجموعات سارة، ثم بقراءة قسمها:
curl -s -G $SCIM/Groups -H "Authorization: Bearer $TOKEN" \
--data-urlencode 'filter=members.value eq "6392bdbc87fc4a53bafa9e8b31967214"' --data-urlencode 'attributes=displayName'
curl -s -G $SCIM/Users/6392bdbc87fc4a53bafa9e8b31967214 -H "Authorization: Bearer $TOKEN" \
--data-urlencode 'attributes=userName,urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department'والمخرج سوف يكون كما يلي:
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
],
"totalResults": 1,
"startIndex": 1,
"itemsPerPage": 1,
"Resources": [
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:Group"
],
"id": "495a6144cc9c400e90873eaf85da1992",
"displayName": "engineering"
}
]
}
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"id": "6392bdbc87fc4a53bafa9e8b31967214",
"userName": "sara",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"department": "Engineering"
}
}سارة الآن في engineering وحدها، ولم تعد في finance، وهذا ما لا يفعله JIT إلا عند دخولها التالي.
المغادرة: التعطيل قبل الحذف
في آخر يوم لا نحذف الحساب، وإنما نرسل active: false:
curl -s -o /dev/null -w "%{http_code}\n" -X PATCH $SCIM/Users/6392bdbc87fc4a53bafa9e8b31967214 \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/scim+json" -d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "replace", "path": "active", "value": false}
]
}'
curl -s -G $SCIM/Users -H "Authorization: Bearer $TOKEN" \
--data-urlencode 'filter=active eq false' --data-urlencode 'attributes=userName,active,meta.lastModified'والمخرج سوف يكون كما يلي:
204
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
],
"totalResults": 1,
"startIndex": 1,
"itemsPerPage": 1,
"Resources": [
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"id": "6392bdbc87fc4a53bafa9e8b31967214",
"meta": {
"lastModified": "2026-10-05T20:21:53.437397Z"
},
"userName": "sara",
"active": false
}
]
}والسبب في أننا نعطل ولا نحذف أن الحساب الموقوف يحتفظ بكل ما يملكه: المستودعات والمستندات والرسائل وسجل من فعل ماذا، فيستطيع المدير أن ينقل ملكيتها لزميل، ويستطيع فريق الأمن أن يراجعها إذا ظهرت حادثة بعد شهر. أما الحذف فيأتي لاحقاً بقرار مكتوب، بطلب DELETE:
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE $SCIM/Users/6392bdbc87fc4a53bafa9e8b31967214 -H "Authorization: Bearer $TOKEN"
curl -s $SCIM/Users/6392bdbc87fc4a53bafa9e8b31967214 -H "Authorization: Bearer $TOKEN"والمخرج سوف يكون كما يلي:
204
{"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "404", "detail": "User '6392bdbc87fc4a53bafa9e8b31967214' not found"}وهذا ما يطلبه القسم 3.6 من RFC 7644: الحذف يرد بـ 204، وكل طلب لاحق على المورد المحذوف يرد بـ 404، ويجوز للتطبيق أن يحتفظ بالبيانات داخلياً بشرط أن يختفي المورد من كل النتائج.
لنكسر شيئاً عن قصد
الآن نرتكب الأخطاء التي سوف تراها في سجلات Logs مزود الهوية عندما يكون الإعداد خاطئاً. الأول رمز خاطئ، والثاني بحث بصيغة غير صحيحة كتبنا فيه = بدل eq:
curl -s $SCIM/Users -H "Authorization: Bearer wrong" -w "\n%{http_code}\n"
curl -s -G $SCIM/Users -H "Authorization: Bearer $TOKEN" --data-urlencode 'filter=userName = "sara"' -w "\n%{http_code}\n"والمخرج سوف يكون كما يلي:
{"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "401", "detail": "Authorization failure. The authorization header is invalid or missing"}
401
{"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "400", "scimType": "invalidFilter", "detail": "invalid syntax at column 10"}
400كل خطأ في SCIM يأتي بالمخطط urn:ietf:params:scim:api:messages:2.0:Error، ومعه status وأحياناً scimType الذي يحدد نوع المشكلة مثل uniqueness وinvalidFilter، فإذا رأيت 401 في سجل مزود الهوية فالرمز خاطئ أو انتهت صلاحيته، وإذا رأيت 409 فهناك حساب بالاسم نفسه أنشئ يدوياً قبل تفعيل SCIM.
وهناك نتيجة ثالثة أهم من الخطأين، فبعد أن حذفنا سارة اقرأ مجموعة engineering:
curl -s -G $SCIM/Groups/495a6144cc9c400e90873eaf85da1992 -H "Authorization: Bearer $TOKEN" --data-urlencode 'attributes=displayName,members'والمخرج سوف يكون كما يلي:
{"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "id": "495a6144cc9c400e90873eaf85da1992", "displayName": "engineering", "members": [{"value": "6392bdbc87fc4a53bafa9e8b31967214"}]}المجموعة ما زالت تشير إلى مستخدم لم يعد موجوداً، فهذا السيرفر لم ينظف العضوية عند الحذف، والمعيار لا يلزمه بذلك صراحة. والدرس أن كل تطبيق يفسر SCIM بطريقته في التفاصيل، حتى أن RFC 7643 نفسه يقول إن المعنى النهائي للحقل active يحدده التطبيق، لذلك لا تفترض أن active: false يلغي الرموز ومفاتيح SSH والجلسات المفتوحة في كل تطبيق، وإنما اختبر ذلك في كل تطبيق قبل أن تعتمد عليه.
وعند الانتهاء احذف السيرفر التجريبي:
docker rm -f scim-testكيف يرسلها authentik في الواقع؟
في التجربة السابقة كنا نحن مزود الهوية، والآن نترك authentik يقوم بالدور نفسه ونراقب ما يصل إلى التطبيق. وقد شغلنا لذلك authentik 2026.8.3 وبجانبه سيرفر SCIM التجريبي نفسه في ملف Docker Compose واحد، وإذا لم يكن authentik مثبتاً لديك فقد شرحنا تثبيته في دليل authentik. والإعداد في authentik يمر بالخطوات التالية كما في توثيق إنشاء مزود SCIM:
- من Applications › Applications اضغط New Application، واختر SCIM نوعاً للمزود Provider Type، ثم ضع عنوان SCIM الأساسي للتطبيق والرمز الذي ولده التطبيق، واترك وضع التوافق Compatibility Mode على Default ما لم يكن التطبيق من الخدمات المذكورة فيه.
- اربط مزود SCIM بالتطبيق الذي يدخل منه المستخدمون عبر OIDC أو SAML، بإضافته في الحقل Backchannel Providers عند تعديل التطبيق.
- اربط التطبيق بالمجموعة التي يجب أن تصل إليه، مثل
finance، لأن authentik يرسل فقط المستخدمين الذين يملكون صلاحية الوصول إلى التطبيق، وحدد في المزود المجموعات التي ترسل إلى التطبيق.
ثم أنشأنا المستخدمة sara في authentik داخل المجموعة finance، وهذا ما وصل إلى سجل التطبيق كما هو:
"GET /v2/ServiceProviderConfig HTTP/1.1" 200 617
"GET /v2/Users?count=100&startIndex=1 HTTP/1.1" 200 139
"GET /v2/Groups?count=100&startIndex=1 HTTP/1.1" 200 139
"POST /v2/Groups HTTP/1.1" 201 416
"GET /v2/ServiceProviderConfig HTTP/1.1" 200 617
"POST /v2/Users HTTP/1.1" 201 729
"POST /v2/Groups HTTP/1.1" 201 402
"GET /v2/Groups/f8e165f08a504f9090cf5b6244407464 HTTP/1.1" 200 402
"DELETE /v2/Users/ac33659e0729418d8b361029cbd3b155 HTTP/1.1" 204 0
"DELETE /v2/Groups/850ed4b8ee204845a7745d91ee340113 HTTP/1.1" 204 0
"POST /v2/Users HTTP/1.1" 201 856
"POST /v2/Users HTTP/1.1" 409 187
"GET /v2/Users?filter=userName+eq+%22sara%22 HTTP/1.1" 200 995
"PATCH /v2/Groups/f8e165f08a504f9090cf5b6244407464 HTTP/1.1" 204 0في السجل أعلاه لاحظ التالي:
- أول ما فعله authentik أنه قرأ
/ServiceProviderConfigثم قرأ المستخدمين والمجموعات الموجودة في التطبيق صفحة بعد صفحة، وهذه المزامنة الكاملة نفسها التي يعيدها كل ساعة. - الطلبان
POSTالأوليان أرسلا حساب المديرakadminومجموعة المديرين إلى التطبيق، ثم حذفهما authentik بعد ثوان بطلبيDELETE. والسبب أننا أنشأنا التطبيق في authentik قبل أن نربطه بمجموعة، والتطبيق غير المربوط بأي مجموعة أو سياسة يستطيع كل مستخدم الوصول إليه، فأرسل authentik كل المستخدمين، وعندما ربطناه بـfinanceحذف من لم يعد يملك الوصول. لذلك اربط التطبيق بمجموعته وحدد المجموعات في المزود قبل أن تضيف مزود SCIM إليه، والسبب أن أول مزامنة ترسل كل من يملك الوصول في تلك اللحظة. - الحساب
saraأرسل مرتين في الثانية نفسها، فرد التطبيق على الثانية بـ409، فبحث authentik عنها بـfilter=userName eq "sara"وربط الحساب الموجود بنفسه، ثم أضافها إلى المجموعة بطلبPATCH، وهذا السلوك نفسه الذي رأيناه في الكود.
والحساب الذي وصل إلى التطبيق يحمل في externalId معرف sara في authentik، وفي emails بريدها، ومعه حقل photos برابط صورتها الرمزية من Gravatar لأن الربط الافتراضي يرسله، فراجع الربط واحذف منه كل حقل لا يحتاجه التطبيق، والسبب أن كل حقل ترسله هو بيانات شخصية تنسخ إلى نظام آخر.
بعد ذلك عطلنا sara في authentik، وهذا ما وصل إلى التطبيق بعد أربع ثوان:
"PUT /v2/Users/73aa42a9a13e45c08bc5d469d1c324fa HTTP/1.1" 200 857لاحظ أن authentik يستخدم PUT ويرسل المستخدم كاملاً وفيه "active": false، ولا يستخدم PATCH للمستخدمين كما فعلنا يدوياً، وكلاهما صحيح في المعيار. ثم أعدنا تفعيلها، وأزلناها من المجموعة finance المربوطة بالتطبيق، فوصل في الثانية التالية:
"DELETE /v2/Users/73aa42a9a13e45c08bc5d469d1c324fa HTTP/1.1" 204 0وهنا الفرق الذي يجب أن تحفظه: تعطيل المستخدم في authentik يرسل active: false، أما سحب صلاحية الوصول إلى التطبيق فيرسل DELETE، والتطبيق الذي ينفذ الحذف فعلاً قد يحذف معه بيانات المستخدم، لذلك اجعل المغادرة تعطيلاً في authentik، ولا تقم بإزالة الموظف من مجموعة التطبيق قبل أن تقرر مصير بياناته فيه.
من يدعم SCIM من الأدوات مفتوحة المصدر؟
هنا يأتي الجزء الذي يحتاج إلى صراحة: SCIM معيار قديم ومنتشر في الخدمات السحابية التجارية، ولكن دعمه في الأدوات مفتوحة المصدر التي نثبتها على سيرفراتنا ما زال محدوداً، وهو في كثير منها ميزة مدفوعة. لذلك افصل بين طرفين: مزود الهوية الذي يرسل، والتطبيق الذي يستقبل، ويجب أن يدعمه الطرفان معاً.
مزودو الهوية: من يرسل SCIM؟
- authentik: يملك مزود SCIM Provider في الإصدار المجاني يرسل المستخدمين والمجموعات إلى التطبيقات، ويعمل مزوداً خلفياً Backchannel Provider يضاف إلى التطبيق بجانب مزود الدخول OIDC أو SAML. ويرسل كل تغيير فوراً ويزامن مزامنة كاملة كل ساعة، ويرسل فقط المستخدمين المرتبطين بالتطبيق مباشرة أو عبر مجموعة، والمصادقة برمز ثابت Static Token في الإصدار المجاني، أما الرموز قصيرة العمر عبر OAuth فهي في الإصدار التجاري Enterprise. ويضبط الحقول عبر ربط الخصائص Property Mappings، والربط الافتراضي يرسل
activeمن حالة الحساب في authentik، ويضع معرف المستخدم في authentik فيexternalId. - Keycloak: أصبح يملك واجهة SCIM مدعومة رسمياً ومفعلة افتراضياً منذ الإصدار 26.8 في أكتوبر 2026، بعد أن كانت تجريبية في الإصدار 26.6، ولكن انتبه للاتجاه: هذه الواجهة تجعل Keycloak سيرفر SCIM يستقبل المستخدمين من نظام آخر على العنوان
/realms/{realm}/scim/v2، ولا تجعله يرسل المستخدمين إلى تطبيقاتك. - Kanidm: تنص صفحة الميزات على أن SCIM مدعوم حالياً للمزامنة من مزود هوية آخر إلى Kanidm فقط، أي استقبالاً وليس إرسالاً.
التطبيقات: من يستقبل SCIM؟
| التطبيق | SCIM | ملاحظات |
|---|---|---|
| GitLab | نعم، في Premium وUltimate | يولد رمز SCIM من لوحة الإدارة، والإلغاء يحظر Block المستخدم ولا يحذفه، كما في التوثيق. |
| Zulip | نعم، في السيرفر الذاتي أيضاً | يفعل من SCIM_CONFIG في إعدادات السيرفر برمز تولده بنفسك، والدعم ما زال تجريبياً Beta، ومزامنة المجموعات غير مدعومة. وفي Zulip Cloud يحتاج إلى خطة Plus كما في صفحة المساعدة. |
| Nextcloud | عبر إضافة | إضافة SCIM Service Provider من IndieHosters وAudriga، وآخر إصداراتها ما زالت Alpha غير مستقرة. |
| Gitea | لا | SCIM مطلوب في قائمة متطلبات SSO المفتوحة. |
| Mattermost | لا | طرق التجهيز هي الإنشاء عند الدخول والاستيراد والواجهة البرمجية، والإلغاء عبر مزامنة AD/LDAP أو لوحة النظام أو الواجهة البرمجية أو mmctl. |
| Outline | لا | ذكر المشرف على المشروع في نقاش طلب الميزة أن SCIM مخطط له ولكنه لن يكون في الإصدار المجتمعي Community Edition. |
| Stalwart | في الإصدار التجاري فقط | كما في صفحة المقارنة، والإصدار المجاني يدعم LDAP وOIDC. |
ولاحظ النمط المتكرر: SCIM والمزامنة مع الدليل وسجلات التدقيق تأتي غالباً في الخطط المدفوعة، كما رأينا في Stalwart وGitLab وOutline، وكما في Dokploy الذي يضع SCIM في ميزات Enterprise. والسبب التجاري واضح، فالمؤسسة التي تحتاج إلى SCIM مؤسسة كبيرة بما يكفي لتدفع، لذلك ضعه في حسابك عند اختيار أي أداة: هل يدعم الإصدار المجاني الطريقة التي سوف تغلق بها حسابات المغادرين؟
وإذا لم يدعم التطبيق SCIM؟
أغلب تطبيقاتك اليوم من هذا النوع، والحل ليس أن تتركها، وإنما أن تختار لكل تطبيق أقرب بديل، بالترتيب التالي:
- مزامنة LDAP Sync: إذا كان التطبيق يدعم LDAP ويستطيع أن يزامن منه دورياً ويعطل من اختفى من الدليل، فاربطه بالدليل، وauthentik نفسه يستطيع أن يعمل سيرفر LDAP عبر LDAP Outpost. ولاحظ أن التعطيل هنا يتأخر حتى المزامنة التالية، وأن بعض التطبيقات تجعل هذه المزامنة ميزة مدفوعة.
- JIT مع تنظيف مجدول: اترك الإنشاء عند الدخول كما هو، واكتب مهمة مجدولة Cron Job تمر كل ليلة على مستخدمي التطبيق عبر واجهته البرمجية أو أداة سطر الأوامر، وتقارنهم بقائمة المستخدمين النشطين في مزود الهوية، وتعطل كل حساب لا يقابله مستخدم نشط، ثم ترسل التقرير إلى المسؤول.
- محول SCIM Adapter: خدمة صغيرة تستقبل SCIM من مزود الهوية وتترجمه إلى واجهة التطبيق، وهو حل جيد إذا كتبته وصنته بنفسك، ولكن عامل أي محول من طرف ثالث كأنه يملك صلاحية مدير على التطبيق، لأنه يملكها فعلاً.
- قائمة المغادرة اليدوية: لما بقي، مع مراجعة دورية كل شهر لكل حساب نشط في كل تطبيق.
الأمان والأخطاء الشائعة
رمز SCIM يستطيع أن ينشئ مستخدمين ويضيفهم إلى أي مجموعة، ومنها مجموعة المديرين، لذلك عامله كأنه كلمة مرور مدير لكل تطبيق مرتبط، وفيما يلي القواعد التي نطبقها:
- احم الرمز: لا تضعه في ملفات الكود ولا في مستودع Git، وضعه في مزود الهوية فقط، وولد رمزاً مختلفاً لكل تطبيق حتى تستطيع أن تلغي رمزاً واحداً دون أن تكسر الباقي، وغيره دورياً وعند مغادرة أي شخص اطلع عليه.
- HTTPS فقط، ومن مصدر واحد: عنوان SCIM في التطبيق لا يحتاج أن يصل إليه أحد غير مزود الهوية، لذلك إذا كان التطبيق والمزود على الشبكة نفسها فاجعل الطلبات تمر داخلها، وإلا فقيد العنوان
/scim/v2في ال Reverse Proxy بعنوان IP لمزود الهوية. - أقل صلاحية Least Privilege: إذا سمح التطبيق بحساب خدمة Service Account أو رمز لا يملك إلا صلاحية التجهيز فاستخدمه بدلاً من رمز مدير كامل، وفي authentik أرسل فقط المستخدمين المرتبطين بالتطبيق، واستبعد حسابات الخدمة بالخيار Exclude service accounts.
- عطل ولا تحذف: اجعل المغادرة تعطيلاً عبر
active: false، والحذف قراراً منفصلاً بحسب سياسة الاحتفاظ، وتذكر ما رأيناه في authentik: إزالة المستخدم من المجموعة المربوطة بالتطبيق ترسلDELETEوليس تعطيلاً. - أرسل أقل ما يمكن: الربط الافتراضي في authentik يرسل الاسم والبريد والصورة الرمزية واللغة، فاحذف من الربط كل حقل لا يحتاجه التطبيق، وأرسل المجموعات التي يحتاجها فقط عبر تحديد المجموعات في المزود.
- معرفات ثابتة: الاسم
userNameوالبريد يتغيران، عند الزواج أو تصحيح الاسم أو تغيير النطاق، أماexternalIdوidفثابتان، لذلك اربط الحسابات بالمعرفات وليس بالأسماء، وإذا ربط التطبيق حساباته بالبريد فاختبر ماذا يحدث عندما يتغير البريد قبل أن يحدث في الواقع. - اختبر على نسخة تجريبية Staging: أول مزامنة كاملة ترسل كل المستخدمين المرتبطين دفعة واحدة، وأي خطأ في ربط الحقول أو في المجموعات يصل إلى كل الحسابات، لذلك جرب الربط على نسخة من التطبيق أو على سيرفر مثل الذي شغلناه أعلاه، ثم ابدأ بمجموعة صغيرة.
- الحسابات خارج SCIM: SCIM يدير الحسابات التي أنشأها فقط، أما الحساب المحلي الذي أنشأه شخص يدوياً فلا يعرف عنه شيئاً، لذلك راجع دورياً الحسابات التي لا تحمل
externalIdأو لا يقابلها مستخدم في مزود الهوية، واحتفظ بحساب طوارئ محلي واحد معروف وموثق. - السجلات Logs والتدقيق Audit: راجع سجل المزامنة في مزود الهوية بعد كل تغيير كبير، فالطلب الفاشل بـ
401أو409يعني أن هناك حساباً لم يعطل، واحفظ سجلات التطبيق التي تبين من أنشأ الحساب ومن عطله ومتى.
الصورة الكاملة: SSO وSCIM وMFA معاً
الآن تستطيع أن ترى إدارة الهوية في مؤسستك كثلاث طبقات تكمل بعضها، ولا تغني واحدة منها عن الأخرى:
- الدخول الموحد SSO: يجيب عن «من يدخل الآن؟»، فكل التطبيقات تثق بمزود هوية واحد، وكلمة المرور تكتب في مكان واحد، كما شرحنا في شرح OAuth وOpenID Connect.
- المصادقة متعددة العوامل MFA: تجيب عن «هل هو فعلاً صاحب الحساب؟»، وتفرض مرة واحدة في مزود الهوية كما في فرض المصادقة الثنائية في authentik.
- SCIM: يجيب عن «ما الحسابات التي يجب أن توجد في كل تطبيق، وبأي مجموعات، وهل ما زالت نشطة؟»، أي أنه يغلق الأبواب التي لا تمر على مزود الهوية.
والصورة التالية تضع طرق وصول الحسابات إلى التطبيق جنباً إلى جنب، والفرق الحقيقي بينها يظهر في اليوم الأخير للموظف:

| يدوياً | JIT | LDAP Sync | SCIM | |
|---|---|---|---|---|
| من يبدأ | مسؤول الأنظمة | المستخدم عند دخوله | التطبيق يسحب من الدليل | مزود الهوية يرسل |
| الحساب قبل أول دخول | نعم إذا تذكر أحدهم | لا | بحسب التطبيق | نعم |
| تغيير المجموعات | يدوياً | عند الدخول التالي | في المزامنة التالية | فوراً |
| إغلاق حساب المغادر | بحسب ذاكرة المسؤول | لا يحدث | في المزامنة التالية إذا دعمه التطبيق | فوراً، مع مراجعة دورية |
| ما يحتاجه التطبيق | لا يحتاج إلى شيء | OIDC أو SAML | دعم LDAP | عنوان SCIM |
| متى تختاره | تطبيقات قليلة جداً | تطبيقات قليلة الحساسية مع تنظيف مجدول | تطبيق يدعم LDAP ولا يدعم SCIM | كلما دعمه التطبيق ومزود الهوية |
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- الدخول الموحد يقرر من يدخل، ولكن الحسابات تعيش داخل كل تطبيق، ومعها الرموز ومفاتيح SSH وروابط المشاركة التي لا تمر على مزود الهوية.
- JIT ينشئ الحساب عند أول دخول ولا يعطله أبداً، والقائمة اليدوية والسكربتات تعتمد على ذاكرة الأشخاص وتنكسر مع التحديثات.
- SCIM معيار مفتوح من 2015 (RFC 7642 وRFC 7643 وRFC 7644) بواجهة REST وJSON، يرسل فيه مزود الهوية بصفته SCIM Client التغيير إلى
/Usersو/Groupsفي كل تطبيق بصفته Service Provider، برمز Bearer عبر HTTPS. - التعيين
POST، والنقلPATCHعلى المجموعات، والمغادرةactive: falseأولاً ثمDELETEبحسب سياسة الاحتفاظ. - authentik يرسل SCIM في إصداره المجاني، أما Keycloak وKanidm فيستقبلانه ولا يرسلانه إلى التطبيقات، وأغلب التطبيقات مفتوحة المصدر لا تدعمه أو تجعله ميزة مدفوعة، فاختر لها LDAP Sync أو JIT مع تنظيف مجدول.
- عامل رمز SCIM كأنه كلمة مرور مدير، واربط الحسابات بالمعرفات الثابتة، واختبر سلوك
active: falseفي كل تطبيق، وقارن دورياً الحسابات بقائمة مزود الهوية.
دليلشرح OAuth وOpenID Connect للمبتدئينلماذا لا يجب أن يعرف أي تطبيق كلمة مرورك، وكيف يحل OAuth ذلك بتوكنات محدودة، وكيف يضيف OpenID Connect الدخول الموحد.
دليلتثبيت authentik للدخول الموحد SSO إلى تطبيقاتككيف تثبت authentik حتى يدخل كل موظف إلى تطبيقات الفريق بحساب واحد، مع فرض المصادقة الثنائية على الجميع.
دليلكيف تستضيف بريد مؤسستك بنفسك، من القرار إلى التشغيلمتى تستضيف بريد شركتك بنفسك ومتى يكون البريد المستضاف أنسب، وكيف تنقل المستخدمين على مراحل دون أن تضيع رسالة.
دليلدخول موحد إلى Gitea بحسابات authentikاجعل موظفيك يدخلون إلى Gitea بحساباتهم في authentik، فتنشأ حساباتهم تلقائياً وتتبع صلاحياتهم المجموعة التي ينتمون إليها.سجل التحديثات
- أكتوبر 2026: كتابة المقال، وتنفيذ أمثلة SCIM على scim2-server 0.6.0، ومراقبة ما يرسله مزود SCIM في authentik 2026.8.3.