> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# ما هو SCIM؟ إدارة حسابات الموظفين تلقائياً في كل تطبيقاتك
- URL: https://arabroot.io/articles/ما-هو-scim-ادارة-الحسابات-تلقائيا/
- Published: 2026-10-05T09:15:00.000Z
- Updated: 2026-10-09T09:41:59.000Z
- Description: الدخول الموحد SSO يمنع الموظف المغادر من الدخول لكن حساباته تبقى نشطة، وSCIM ينشئ الحسابات ويعطلها في كل تطبيق تلقائياً.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, الأمن السيبراني, الاستضافة الذاتية

في أول يوم لموظفة جديدة في قسم المالية تحتاج إلى حساب في البريد، وفي سيرفر Git، وفي قاعدة المعرفة Wiki، وفي تطبيق المحادثة Chat، وفي نظام التذاكر، وربما في خمسة أنظمة أخرى، وفي آخر يوم لها يجب أن تغلق كل هذه الحسابات في الساعة نفسها. وإذا كنت قد ربطت تطبيقاتك بمزود هوية Identity Provider مثل [authentik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-authentik-%D9%84%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%A7%D9%84%D9%85%D9%88%D8%AD%D8%AF/) فقد حللت نصف المشكلة فقط، لأن الدخول الموحد Single Sign-On واختصاراً SSO يجيب عن سؤال «من الذي يسجل دخوله الآن؟»، ولكنه لا يجيب عن سؤال «ما الحسابات التي يجب أن توجد داخل كل تطبيق، وبأي صلاحيات، ومتى تغلق؟».

وهذا النصف الثاني له اسم هو تجهيز الحسابات Provisioning وإلغاؤها Deprovisioning، وله معيار مفتوح اسمه **SCIM**، وهو موضوع هذا المقال. وإذا لم تكن تعرف بعد كيف يعمل الدخول الموحد نفسه فابدأ بشرح [OAuth وOpenID Connect للمبتدئين](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)، لأننا هنا نبني عليه ولا نكرره، وسوف نناقش في هذا المقال ما يلي:

- الفجوة التي يتركها 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 التي أنشأها.

والصورة التالية تبين هذه الفجوة: المدير يعطل الحساب في مزود الهوية فيرفض الدخول الموحد، ولكن لا تصل أي رسالة إلى التطبيقات، فتبقى الحسابات فيها نشطة، والرمز المحفوظ على حاسوب الموظفة السابقة ما زال يعمل:

![مخطط يبين مزود الهوية authentik وفيه حساب sara معطلاً فيرفض دخولها عبر SSO، وتحته ثلاثة تطبيقات Gitea وOutline وMattermost ما زال حساب sara نشطاً في كل منها، وحاسوبها يرسل git push برمز محفوظ إلى Gitea مباشرة فينجح](https://arabroot.io/content/images/2026/10/scim-01-the-gap.webp)

تعطيل الحساب في مزود الهوية يغلق باب الدخول الموحد فقط، والحسابات داخل التطبيقات تبقى كما هي

وقد ذكرنا هذه النقطة بالتفصيل في [ربط Gitea مع authentik](https://arabroot.io/articles/%D8%B1%D8%A8%D8%B7-gitea-%D9%85%D8%B9-authentik-%D8%B9%D8%A8%D8%B1-oidc/#security)، حيث تبقى مفاتيح SSH والرموز صالحة بعد تعطيل الحساب في authentik لأنها لا تمر عليه، وفي [دليل استضافة البريد للمؤسسات](https://arabroot.io/articles/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D9%84%D9%84%D9%85%D8%A4%D8%B3%D8%B3%D8%A7%D8%AA/#offboarding) حيث يبقى صندوق بريد الموظف المغادر يستقبل الرسائل. والسؤال الآن: كيف تصل الحسابات إلى التطبيقات وكيف تغلق؟ وفيما يلي الحلول التي يجربها أغلب الفرق بالترتيب.

### الحل الأول: قائمة مهام يدوية

أبسط حل هو قائمة تحقق Checklist يمر عليها مسؤول الأنظمة عند كل تعيين ومغادرة: ينشئ الحساب في كل تطبيق ويضيفه إلى مجموعاته، ثم يعطله في كل تطبيق عند المغادرة. وهذا الحل يعمل مع خمسة موظفين وثلاثة تطبيقات، ولكنه يفشل مع النمو، والسبب أن كل خطوة فيه تعتمد على ذاكرة شخص: تطبيق أضيف الشهر الماضي ولم يدخل القائمة، وطلب مغادرة وصل يوم الخميس فنفذ يوم الأحد، ومسؤول في إجازة. والأسوأ أنك لا تملك طريقة تتأكد بها أن الحسابات في التطبيقات تطابق الواقع، فلا أحد يعرف كم حساباً نشطاً لموظفين غادروا قبل سنة.

### الحل الثاني: الإنشاء التلقائي عند أول دخول JIT

أغلب التطبيقات التي تدعم OIDC أو SAML تستطيع أن تنشئ الحساب تلقائياً عند أول دخول عبر مزود الهوية، وهذا ما يسمى الإنشاء في الوقت المناسب Just-in-Time Provisioning واختصاراً JIT، وقد فعلناه في Gitea عبر خيار التسجيل التلقائي Auto Registration. وهذا الحل يحل مشكلة التعيين جزئياً، ولكن لاحظ ما لا يفعله:

- **لا ينشئ الحساب قبل الدخول:** الحساب غير موجود حتى يدخل صاحبه بنفسه، فلا تستطيع أن تضيفه إلى فريق أو تشاركه مستنداً قبل ذلك، وفي البريد يعني ذلك أن الرسائل إليه ترفض، كما يوضح توثيق Stalwart في حالة [OIDC](https://stalw.art/docs/auth/backend/oidc/?ref=arabroot.io).
- **التغيير لا يصل إلا عند الدخول التالي:** إذا سحبت من الموظف مجموعة `gitea-admins` في authentik فإن Gitea يسحب صلاحية المدير عند دخوله التالي فقط، والجلسة المفتوحة تبقى بصلاحيتها القديمة.
- **لا يحذف شيئاً ولا يعطله:** وهذه أخطر نقطة، فمن لا يدخل لا يرسل شيئاً، والموظف الذي غادر لن يدخل مرة أخرى عبر مزود الهوية، فيبقى حسابه في التطبيق كما هو. وقد نص على ذلك صراحة [توثيق Stalwart](https://stalw.art/docs/auth/backend/ldap/?ref=arabroot.io)، ويتتبع فريق Gitea المشكلة نفسها في [قائمة متطلبات الدخول الموحد](https://github.com/go-gitea/gitea/issues/23794?ref=arabroot.io) حيث يذكر أن الحساب الذي يحذف من 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](https://datatracker.ietf.org/doc/html/rfc7642?ref=arabroot.io): المفاهيم وحالات الاستخدام، وهي وثيقة معلوماتية Informational تشرح السيناريوهات ومنها التعيين والنقل والمغادرة Joiner, Mover, Leaver.
- [RFC 7643](https://datatracker.ietf.org/doc/html/rfc7643?ref=arabroot.io): المخطط Schema، أي شكل المستخدم والمجموعة وأسماء حقولهما وقواعد كل حقل.
- [RFC 7644](https://datatracker.ietf.org/doc/html/rfc7644?ref=arabroot.io): البروتوكول Protocol، أي العناوين Endpoints والطلبات والردود والأخطاء والبحث والتعديل الجزئي.

والجملة التي تلخص الفكرة كلها موجودة في RFC 7642 نفسه، حيث يذكر أن SCIM يقوم بتجهيز الحسابات وإلغائها في سياق منفصل عن المصادقة Authentication، بخلاف الإنشاء عند الدخول. وهذا يعني أن مزود الهوية لا ينتظر أن يدخل المستخدم، وإنما يرسل التغيير بنفسه إلى كل تطبيق في اللحظة التي يحدث فيها.

ولو أردنا تشبيهاً من الحياة فالدخول الموحد هو بوابة المبنى التي تتحقق من بطاقتك عند الدخول، أما SCIM فهو التعميم الذي ترسله الموارد البشرية إلى كل قسم: «انضمت سارة إلى المالية، جهزوا مكتبها»، ثم «انتقلت إلى التطوير»، ثم «انتهت خدمتها، استلموا مفاتيحها». والبوابة وحدها لا تسترجع مفتاح الخزانة الذي بقي في جيب الموظفة.

![مخطط يبين مكتب الموارد البشرية يرسل حدث التعيين أو النقل أو المغادرة إلى authentik بصفته SCIM Client، ثم يرسل authentik طلبات POST /Users وPATCH /Groups وPATCH active=false وGET /Users?filter عبر HTTPS برمز Bearer إلى تطبيقات تعمل SCIM Service Providers مثل GitLab وZulip والخدمات السحابية وتطبيقك الخاص، وتطبيق بلا SCIM يحتاج إلى LDAP أو JIT أو واجهته البرمجية](https://arabroot.io/content/images/2026/10/scim-02-scim-push.webp)

مزود الهوية هو الكلاينت الذي يرسل، وكل تطبيق هو سيرفر يستقبل على عنوان 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 مزامنة كاملة مرة كل ساعة](https://docs.goauthentik.io/add-secure-apps/providers/scim/?ref=arabroot.io).

والصورة التالية تجمع هذه المكونات داخل سيرفر SCIM واحد:

![مخطط يبين سيرفر SCIM وعنوانه الأساسي، وتحته ثلاثة عناوين للاكتشاف ServiceProviderConfig وResourceTypes وSchemas، وعنوانان للبيانات Users وGroups، وبجانبهما مورد مستخدم فيه id يضعه التطبيق وexternalId يرسله مزود الهوية وuserName وemails وactive وامتداد المؤسسة، ومورد مجموعة أعضاؤه معرفات المستخدمين](https://arabroot.io/content/images/2026/10/scim-03-anatomy.webp)

عناوين الاكتشاف تخبر الكلاينت بما يدعمه التطبيق، و`id` من التطبيق و`externalId` من مزود الهوية

## دورة حياة الحساب مع SCIM: من أول يوم إلى آخر يوم

لنأخذ الموظفة نفسها، سارة، ونتبعها في ثلاث مراحل، ولاحظ أن المدير في كل مرحلة يعمل في مكان واحد فقط هو مزود الهوية، والباقي يرسله SCIM:

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

![مخطط زمني بثلاث مراحل: في اليوم الأول ترسل طلبات POST /Users وPATCH لإضافة sara إلى مجموعة finance فينشأ حسابها نشطاً في Git وWiki وChat، وفي الشهر الثامن تنقل من finance إلى engineering بطلبي PATCH، وفي اليوم الأخير يرسل PATCH active=false فيصبح حسابها معطلاً في التطبيقات الثلاثة](https://arabroot.io/content/images/2026/10/scim-04-lifecycle.webp)

ثلاثة أحداث في مزود الهوية، وكل حدث يصل إلى كل التطبيقات فور حدوثه

والجدول التالي يقارن ما يحدث في كل مرحلة بين العمل اليدوي والإنشاء عند الدخول وSCIM:

| المرحلة                  | يدوياً                                      | JIT                            | SCIM                                                                    |
| ------------------------ | ------------------------------------------- | ------------------------------ | ----------------------------------------------------------------------- |
| التعيين                  | بعد أن ينفذ أحدهم القائمة، وقد ينسى تطبيقاً | عند أول دخول للموظف، وليس قبله | قبل أول دخول، في كل تطبيق مرتبط                                         |
| النقل                    | غالباً لا يحدث، فتتراكم الصلاحيات           | عند الدخول التالي فقط          | فور التغيير في مزود الهوية                                              |
| المغادرة                 | بحسب ذاكرة المسؤول وسرعته                   | لا يحدث أبداً                  | تعطيل فوري في كل تطبيق، ومراجعة كاملة كل ساعة في authentik              |
| هل تعرف الحالة الحقيقية؟ | لا، إلا بمراجعة كل تطبيق يدوياً             | لا                             | نعم بقدر ما يدعم التطبيق، لأن مزود الهوية يقارن ما أرسله بما في التطبيق |

## تجربة عملية: نرسل طلبات SCIM بأنفسنا

أفضل طريقة لفهم SCIM أن تكون أنت مزود الهوية لدقائق، فترسل الطلبات بنفسك وترى الردود. وسوف نستخدم [scim2-server](https://github.com/python-scim/scim2-server?ref=arabroot.io)، وهو سيرفر SCIM مرجعي مفتوح المصدر مكتوب بلغة Python يطبق RFC 7643 وRFC 7644 ويحفظ البيانات في الذاكرة، وصمم أصلاً لاختبار كلاينت SCIM، وهذا بالضبط ما نحتاجه. والمثال التالي يشغله في Container على جهازك برمز ثابت:

```bash
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` فقط، فلا يصل إليه أحد من الشبكة، وهذا سيرفر تجارب يحفظ كل شيء في الذاكرة ويفقده عند إيقافه. ونبدأ كما يبدأ أي مزود هوية، أي بسؤال التطبيق عما يدعمه:

```bash
curl -s $SCIM/ServiceProviderConfig -H "Authorization: Bearer $TOKEN"
```

والمخرج سوف يكون كما يلي:

```json
{"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`:

```bash
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": "sara@example.com", "type": "work", "primary": true}],
  "active": true,
  "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {"employeeNumber": "1042", "department": "Finance"}
}'
```

والمخرج سوف يكون كما يلي (بعد ترتيب ال JSON للقراءة):

```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": "sara@example.com",
      "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 يستطيع الكلاينت أن يرسلها مع التعديل حتى لا يكتب فوق تعديل لم يره.

ماذا يحدث إذا أرسل مزود الهوية الطلب نفسه مرة ثانية، بسبب انقطاع في الشبكة مثلاً؟

```bash
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`:

```json
{
  "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](https://github.com/goauthentik/authentik/blob/main/authentik/providers/scim/clients/users.py?ref=arabroot.io)، فإذا رد التطبيق بأن المستخدم موجود وكان البحث مدعوماً أرسل الطلب التالي ليجده.

### البحث عن مستخدم

```bash
curl -s -G $SCIM/Users -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'filter=userName eq "sara"' \
  --data-urlencode 'attributes=userName,active,emails'
```

والمخرج سوف يكون كما يلي:

```json
{
  "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": "sara@example.com",
          "type": "work",
          "primary": true
        }
      ]
    }
  ]
}
```

لاحظ أن الرد قائمة `ListResponse` حتى لو كانت النتيجة واحدة، وأن المعامل `attributes` قصر الرد على الحقول التي طلبناها، وهذا مهم عندما يمر مزود الهوية على آلاف المستخدمين في المزامنة الدورية.

### المجموعات: إضافة عضو بطلب PATCH

ننشئ مجموعة `finance` كما ينشئها مزود الهوية:

```bash
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":[]}'
```

والمخرج سوف يكون كما يلي:

```json
{
  "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` في التطبيق وليس باسمه:

```bash
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\. وللتأكد نسأل عن المجموعات التي تضم سارة:

```bash
curl -s -G $SCIM/Groups -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'filter=members.value eq "6392bdbc87fc4a53bafa9e8b31967214"' \
  --data-urlencode 'attributes=displayName'
```

والمخرج سوف يكون كما يلي:

```json
{
    "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`:

```bash
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"}
  ]
}'
```

والمخرج سوف يكون كما يلي:

```bash
204
204
204
```

ونتحقق من النتيجة بالبحث نفسه عن مجموعات سارة، ثم بقراءة قسمها:

```bash
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'
```

والمخرج سوف يكون كما يلي:

```json
{
  "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`:

```bash
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'
```

والمخرج سوف يكون كما يلي:

```json
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`:

```bash
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"
```

والمخرج سوف يكون كما يلي:

```json
204
{"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "404", "detail": "User '6392bdbc87fc4a53bafa9e8b31967214' not found"}
```

وهذا ما يطلبه [القسم 3.6 من RFC 7644](https://datatracker.ietf.org/doc/html/rfc7644?ref=arabroot.io#section-3.6): الحذف يرد بـ `204`، وكل طلب لاحق على المورد المحذوف يرد بـ `404`، ويجوز للتطبيق أن يحتفظ بالبيانات داخلياً بشرط أن يختفي المورد من كل النتائج.

### لنكسر شيئاً عن قصد

الآن نرتكب الأخطاء التي سوف تراها في سجلات Logs مزود الهوية عندما يكون الإعداد خاطئاً. الأول رمز خاطئ، والثاني بحث بصيغة غير صحيحة كتبنا فيه `=` بدل `eq`:

```bash
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"
```

والمخرج سوف يكون كما يلي:

```json
{"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`:

```bash
curl -s -G $SCIM/Groups/495a6144cc9c400e90873eaf85da1992 -H "Authorization: Bearer $TOKEN" --data-urlencode 'attributes=displayName,members'
```

والمخرج سوف يكون كما يلي:

```json
{"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "id": "495a6144cc9c400e90873eaf85da1992", "displayName": "engineering", "members": [{"value": "6392bdbc87fc4a53bafa9e8b31967214"}]}
```

المجموعة ما زالت تشير إلى مستخدم لم يعد موجوداً، فهذا السيرفر لم ينظف العضوية عند الحذف، والمعيار لا يلزمه بذلك صراحة. والدرس أن كل تطبيق يفسر SCIM بطريقته في التفاصيل، حتى أن RFC 7643 نفسه يقول إن المعنى النهائي للحقل `active` يحدده التطبيق، لذلك لا تفترض أن `active: false` يلغي الرموز ومفاتيح SSH والجلسات المفتوحة في كل تطبيق، وإنما اختبر ذلك في كل تطبيق قبل أن تعتمد عليه.

وعند الانتهاء احذف السيرفر التجريبي:

```bash
docker rm -f scim-test
```

## كيف يرسلها authentik في الواقع؟

في التجربة السابقة كنا نحن مزود الهوية، والآن نترك authentik يقوم بالدور نفسه ونراقب ما يصل إلى التطبيق. وقد شغلنا لذلك authentik 2026.8.3 وبجانبه سيرفر SCIM التجريبي نفسه في ملف Docker Compose واحد، وإذا لم يكن authentik مثبتاً لديك فقد شرحنا تثبيته في [دليل authentik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-authentik-%D9%84%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%A7%D9%84%D9%85%D9%88%D8%AD%D8%AF/). والإعداد في authentik يمر بالخطوات التالية كما في [توثيق إنشاء مزود SCIM](https://docs.goauthentik.io/add-secure-apps/providers/scim/create-scim-provider/?ref=arabroot.io):

1. من **Applications › Applications** اضغط **New Application**، واختر **SCIM** نوعاً للمزود Provider Type، ثم ضع عنوان SCIM الأساسي للتطبيق والرمز الذي ولده التطبيق، واترك وضع التوافق Compatibility Mode على Default ما لم يكن التطبيق من الخدمات المذكورة فيه.
2. اربط مزود SCIM بالتطبيق الذي يدخل منه المستخدمون عبر OIDC أو SAML، بإضافته في الحقل **Backchannel Providers** عند تعديل التطبيق.
3. اربط التطبيق بالمجموعة التي يجب أن تصل إليه، مثل `finance`، لأن authentik يرسل فقط المستخدمين الذين يملكون صلاحية الوصول إلى التطبيق، وحدد في المزود المجموعات التي ترسل إلى التطبيق.

ثم أنشأنا المستخدمة `sara` في authentik داخل المجموعة `finance`، وهذا ما وصل إلى سجل التطبيق كما هو:

```bash
"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، وهذا ما وصل إلى التطبيق بعد أربع ثوان:

```bash
"PUT /v2/Users/73aa42a9a13e45c08bc5d469d1c324fa HTTP/1.1" 200 857
```

لاحظ أن authentik يستخدم `PUT` ويرسل المستخدم كاملاً وفيه `"active": false`، ولا يستخدم PATCH للمستخدمين كما فعلنا يدوياً، وكلاهما صحيح في المعيار. ثم أعدنا تفعيلها، وأزلناها من المجموعة `finance` المربوطة بالتطبيق، فوصل في الثانية التالية:

```bash
"DELETE /v2/Users/73aa42a9a13e45c08bc5d469d1c324fa HTTP/1.1" 204 0
```

وهنا الفرق الذي يجب أن تحفظه: **تعطيل المستخدم في authentik يرسل `active: false`، أما سحب صلاحية الوصول إلى التطبيق فيرسل `DELETE`**، والتطبيق الذي ينفذ الحذف فعلاً قد يحذف معه بيانات المستخدم، لذلك اجعل المغادرة تعطيلاً في authentik، ولا تقم بإزالة الموظف من مجموعة التطبيق قبل أن تقرر مصير بياناته فيه.

## من يدعم SCIM من الأدوات مفتوحة المصدر؟

هنا يأتي الجزء الذي يحتاج إلى صراحة: SCIM معيار قديم ومنتشر في الخدمات السحابية التجارية، ولكن دعمه في الأدوات مفتوحة المصدر التي نثبتها على سيرفراتنا ما زال محدوداً، وهو في كثير منها ميزة مدفوعة. لذلك افصل بين طرفين: مزود الهوية الذي يرسل، والتطبيق الذي يستقبل، ويجب أن يدعمه الطرفان معاً.

### مزودو الهوية: من يرسل SCIM؟

- **authentik:** يملك [مزود SCIM Provider](https://docs.goauthentik.io/add-secure-apps/providers/scim/?ref=arabroot.io) في الإصدار المجاني يرسل المستخدمين والمجموعات إلى التطبيقات، ويعمل مزوداً خلفياً Backchannel Provider يضاف إلى التطبيق بجانب مزود الدخول OIDC أو SAML. ويرسل كل تغيير فوراً ويزامن مزامنة كاملة كل ساعة، ويرسل فقط المستخدمين المرتبطين بالتطبيق مباشرة أو عبر مجموعة، والمصادقة برمز ثابت Static Token في الإصدار المجاني، أما الرموز قصيرة العمر عبر OAuth فهي في الإصدار التجاري Enterprise. ويضبط الحقول عبر ربط الخصائص [Property Mappings](https://docs.goauthentik.io/add-secure-apps/providers/property-mappings/?ref=arabroot.io)، والربط الافتراضي يرسل `active` من حالة الحساب في authentik، ويضع معرف المستخدم في authentik في `externalId`.
- **Keycloak:** أصبح يملك [واجهة SCIM مدعومة رسمياً ومفعلة افتراضياً](https://www.keycloak.org/2026/10/keycloak-2680-released?ref=arabroot.io) منذ الإصدار 26.8 في أكتوبر 2026، بعد أن كانت [تجريبية في الإصدار 26.6](https://www.keycloak.org/2026/04/scim-as-experimental-feature?ref=arabroot.io)، ولكن انتبه للاتجاه: هذه الواجهة تجعل Keycloak سيرفر SCIM يستقبل المستخدمين من نظام آخر على العنوان `/realms/{realm}/scim/v2`، ولا تجعله يرسل المستخدمين إلى تطبيقاتك.
- **Kanidm:** تنص [صفحة الميزات](https://kanidm.github.io/kanidm/stable/supported%5Ffeatures.html?ref=arabroot.io) على أن SCIM مدعوم حالياً للمزامنة من مزود هوية آخر إلى Kanidm فقط، أي استقبالاً وليس إرسالاً.

### التطبيقات: من يستقبل SCIM؟

| التطبيق                                                                                                                                                                                  | SCIM                         | ملاحظات                                                                                                                                                                                                                                                                                                     |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| GitLab                                                                                                                                                                                   | نعم، في Premium وUltimate    | يولد رمز SCIM من لوحة الإدارة، والإلغاء يحظر Block المستخدم ولا يحذفه، كما في [التوثيق](https://docs.gitlab.com/administration/settings/scim%5Fsetup/?ref=arabroot.io).                                                                                                                                     |
| Zulip                                                                                                                                                                                    | نعم، في السيرفر الذاتي أيضاً | يفعل من SCIM\_CONFIG في [إعدادات السيرفر](https://zulip.readthedocs.io/en/latest/production/scim.html?ref=arabroot.io) برمز تولده بنفسك، والدعم ما زال تجريبياً Beta، ومزامنة المجموعات غير مدعومة. وفي Zulip Cloud يحتاج إلى خطة Plus كما في [صفحة المساعدة](https://zulip.com/help/scim?ref=arabroot.io). |
| Nextcloud                                                                                                                                                                                | عبر إضافة                    | إضافة [SCIM Service Provider](https://apps.nextcloud.com/apps/scimserviceprovider?ref=arabroot.io) من IndieHosters وAudriga، وآخر إصداراتها ما زالت Alpha غير مستقرة.                                                                                                                                       |
| [Gitea](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-gitea-%D9%85%D8%B9-docker-compose/)                                                                                  | لا                           | SCIM مطلوب في [قائمة متطلبات SSO](https://github.com/go-gitea/gitea/issues/23794?ref=arabroot.io) المفتوحة.                                                                                                                                                                                                 |
| [Mattermost](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-mattermost-%D9%88%D8%AA%D8%B1%D9%82%D9%8A%D8%AA%D9%87/)                                                         | لا                           | [طرق التجهيز](https://docs.mattermost.com/administration-guide/onboard/user-provisioning-workflows.html?ref=arabroot.io) هي الإنشاء عند الدخول والاستيراد والواجهة البرمجية، والإلغاء عبر مزامنة AD/LDAP أو لوحة النظام أو الواجهة البرمجية أو mmctl.                                                       |
| [Outline](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-outline-%D9%84%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D9%85%D8%B9%D8%B1%D9%81%D8%A9-%D8%A7%D9%84%D9%81%D8%B1%D9%8A%D9%82/) | لا                           | ذكر المشرف على المشروع في [نقاش طلب الميزة](https://github.com/outline/outline/discussions/5821?ref=arabroot.io) أن SCIM مخطط له ولكنه لن يكون في الإصدار المجتمعي Community Edition.                                                                                                                       |
| [Stalwart](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-stalwart/)                                          | في الإصدار التجاري فقط       | كما في [صفحة المقارنة](https://stalw.art/compare/?ref=arabroot.io)، والإصدار المجاني يدعم LDAP وOIDC.                                                                                                                                                                                                       |

ولاحظ النمط المتكرر: SCIM والمزامنة مع الدليل وسجلات التدقيق تأتي غالباً في الخطط المدفوعة، كما رأينا في Stalwart وGitLab وOutline، وكما في [Dokploy](https://docs.dokploy.com/docs/core/enterprise?ref=arabroot.io) الذي يضع SCIM في ميزات Enterprise. والسبب التجاري واضح، فالمؤسسة التي تحتاج إلى SCIM مؤسسة كبيرة بما يكفي لتدفع، لذلك ضعه في حسابك عند اختيار أي أداة: هل يدعم الإصدار المجاني الطريقة التي سوف تغلق بها حسابات المغادرين؟

### وإذا لم يدعم التطبيق SCIM؟

أغلب تطبيقاتك اليوم من هذا النوع، والحل ليس أن تتركها، وإنما أن تختار لكل تطبيق أقرب بديل، بالترتيب التالي:

1. **مزامنة LDAP Sync:** إذا كان التطبيق يدعم LDAP ويستطيع أن يزامن منه دورياً ويعطل من اختفى من الدليل، فاربطه بالدليل، وauthentik نفسه يستطيع أن يعمل سيرفر LDAP عبر LDAP Outpost. ولاحظ أن التعطيل هنا يتأخر حتى المزامنة التالية، وأن بعض التطبيقات تجعل هذه المزامنة ميزة مدفوعة.
2. **JIT مع تنظيف مجدول:** اترك الإنشاء عند الدخول كما هو، واكتب مهمة مجدولة Cron Job تمر كل ليلة على مستخدمي التطبيق عبر واجهته البرمجية أو أداة سطر الأوامر، وتقارنهم بقائمة المستخدمين النشطين في مزود الهوية، وتعطل كل حساب لا يقابله مستخدم نشط، ثم ترسل التقرير إلى المسؤول.
3. **محول SCIM Adapter:** خدمة صغيرة تستقبل SCIM من مزود الهوية وتترجمه إلى واجهة التطبيق، وهو حل جيد إذا كتبته وصنته بنفسك، ولكن عامل أي محول من طرف ثالث كأنه يملك صلاحية مدير على التطبيق، لأنه يملكها فعلاً.
4. **قائمة المغادرة اليدوية:** لما بقي، مع مراجعة دورية كل شهر لكل حساب نشط في كل تطبيق.

## الأمان والأخطاء الشائعة

رمز 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` يعني أن هناك حساباً لم يعطل، واحفظ سجلات التطبيق التي تبين من أنشأ الحساب ومن عطله ومتى.

📌

في مارس 2020 أنهت شركة لتغليف الأجهزة الطبية في ولاية جورجيا خدمة نائب رئيس المالية Christopher Dobbins، وأغلقت حسابه في أنظمتها. ولكنه كان قد أنشأ أثناء عمله حساباً وهمياً بصلاحياته، وفي 29 مارس 2020، بعد ثلاثة أيام من استلامه آخر راتب، دخل به ثم أنشأ حساباً وهمياً ثانياً وعدل منه نحو 115,581 سجلاً وحذف نحو 2,371 سجلاً في نظام الشحن، فتأخر شحن معدات الوقاية الشخصية أثناء جائحة كورونا، وحكم عليه بالسجن سنة ويوماً وتعويض قدره 221,200 دولار كما في [بيان وزارة العدل الأمريكية](https://www.justice.gov/usao-ndga/pr/former-employee-medical-packaging-company-sentenced-federal-prison-disrupting-ppe?ref=arabroot.io). والدرس أن الحساب الذي لم يمر على مزود الهوية لا يغلقه SCIM ولا غيره، لذلك قارن دورياً كل الحسابات في كل تطبيق بقائمة المستخدمين في مزود الهوية.

## الصورة الكاملة: SSO وSCIM وMFA معاً

الآن تستطيع أن ترى إدارة الهوية في مؤسستك كثلاث طبقات تكمل بعضها، ولا تغني واحدة منها عن الأخرى:

- **الدخول الموحد SSO:** يجيب عن «من يدخل الآن؟»، فكل التطبيقات تثق بمزود هوية واحد، وكلمة المرور تكتب في مكان واحد، كما شرحنا في [شرح OAuth وOpenID Connect](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/).
- **المصادقة متعددة العوامل MFA:** تجيب عن «هل هو فعلاً صاحب الحساب؟»، وتفرض مرة واحدة في مزود الهوية كما في [فرض المصادقة الثنائية في authentik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-authentik-%D9%84%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%A7%D9%84%D9%85%D9%88%D8%AD%D8%AF/#mfa).
- **SCIM:** يجيب عن «ما الحسابات التي يجب أن توجد في كل تطبيق، وبأي مجموعات، وهل ما زالت نشطة؟»، أي أنه يغلق الأبواب التي لا تمر على مزود الهوية.

والصورة التالية تضع طرق وصول الحسابات إلى التطبيق جنباً إلى جنب، والفرق الحقيقي بينها يظهر في اليوم الأخير للموظف:

![مخطط من ثلاثة أعمدة: في JIT ينشئ التطبيق الحساب عند أول دخول ويبقى بعد المغادرة نشطاً، وفي LDAP Sync يسحب التطبيق من الدليل دورياً فيعطل الحساب في المزامنة التالية إذا كان التطبيق يفعل ذلك، وفي SCIM يرسل مزود الهوية التغيير فوراً فينشأ الحساب قبل أول دخول ويعطل عند المغادرة](https://arabroot.io/content/images/2026/10/scim-05-jit-ldap-scim.webp)

JIT ينشئ ولا يغلق، وLDAP Sync يغلق متأخراً إذا دعمه التطبيق، و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` في كل تطبيق، وقارن دورياً الحسابات بقائمة مزود الهوية.

[![](https://arabroot.io/content/images/2026/10/oauth-oidc-cover-cover.webp)دليلشرح OAuth وOpenID Connect للمبتدئينلماذا لا يجب أن يعرف أي تطبيق كلمة مرورك، وكيف يحل OAuth ذلك بتوكنات محدودة، وكيف يضيف OpenID Connect الدخول الموحد.](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/authentik-cover-cover-1.webp)دليلتثبيت authentik للدخول الموحد SSO إلى تطبيقاتككيف تثبت authentik حتى يدخل كل موظف إلى تطبيقات الفريق بحساب واحد، مع فرض المصادقة الثنائية على الجميع.](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-authentik-%D9%84%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%A7%D9%84%D9%85%D9%88%D8%AD%D8%AF/)[![](https://arabroot.io/content/images/2026/10/email-hosting-guide-cover-cover.webp)دليلكيف تستضيف بريد مؤسستك بنفسك، من القرار إلى التشغيلمتى تستضيف بريد شركتك بنفسك ومتى يكون البريد المستضاف أنسب، وكيف تنقل المستخدمين على مراحل دون أن تضيع رسالة.](https://arabroot.io/articles/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D9%84%D9%84%D9%85%D8%A4%D8%B3%D8%B3%D8%A7%D8%AA/)[![](https://arabroot.io/content/images/2026/10/gitea-authentik-cover-cover.webp)دليلدخول موحد إلى Gitea بحسابات authentikاجعل موظفيك يدخلون إلى Gitea بحساباتهم في authentik، فتنشأ حساباتهم تلقائياً وتتبع صلاحياتهم المجموعة التي ينتمون إليها.](https://arabroot.io/articles/%D8%B1%D8%A8%D8%B7-gitea-%D9%85%D8%B9-authentik-%D8%B9%D8%A8%D8%B1-oidc/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، وتنفيذ أمثلة SCIM على scim2-server 0.6.0، ومراقبة ما يرسله مزود SCIM في authentik 2026.8.3.