<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>OAuth2.0 on kenji.blog</title><link>http://kenji.blog/ar/tags/oauth2.0/</link><description>Recent content in OAuth2.0 on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ar</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 12:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ar/tags/oauth2.0/index.xml" rel="self" type="application/rss+xml"/><item><title>شرح آلية عمل OAuth 2.0: التعلم من خلال تنفيذ تكامل Slack App</title><link>http://kenji.blog/ar/p/oauth2-architecture-slack-app-integration/</link><pubDate>Sun, 13 Sep 2026 12:00:00 +0900</pubDate><guid>http://kenji.blog/ar/p/oauth2-architecture-slack-app-integration/</guid><description>&lt;img src="http://kenji.blog/p/oauth2-architecture-slack-app-integration/img/eyecatch.jpg" alt="Featured image of post شرح آلية عمل OAuth 2.0: التعلم من خلال تنفيذ تكامل Slack App" />&lt;h1 id="مقدمة-لماذا-نتعلم-oauth-20">مقدمة: لماذا نتعلم OAuth 2.0؟
&lt;/h1>&lt;p>في تطبيقات الويب الحديثة، أصبح من المعتاد جداً أن تعمل خدمات متعددة معاً. على سبيل المثال، ميزات مثل &amp;ldquo;تسجيل الدخول باستخدام حساب Google&amp;rdquo; أو &amp;ldquo;إرسال إشعار إلى Slack عند تحديث مهمة في Trello&amp;rdquo; أو &amp;ldquo;إضافة رابط اجتماع Zoom تلقائياً إلى تقويم Google&amp;rdquo;. الإطار الذي يعمل خلف الكواليس لتنفيذ كل هذا هو إطار التفويض &lt;strong>OAuth 2.0 (Open Authorization 2.0)&lt;/strong>.&lt;/p>
&lt;p>في الماضي، عند تبادل البيانات بين الخدمات المختلفة، تم استخدام طرق خطيرة للغاية مثل &amp;ldquo;المصادقة الأساسية&amp;rdquo; أو &amp;ldquo;مشاركة كلمة المرور&amp;rdquo;، حيث يقوم المستخدم بإعطاء معرّفه وكلمة المرور الخاصة به مباشرة إلى الخدمة المتكاملة. ومع ذلك، تؤدي هذه الطريقة إلى منح الخدمة الأخرى صلاحيات المستخدم الكاملة، مما يشكل خطراً أمنياً فادحاً.&lt;/p>
&lt;p>تم إنشاء OAuth 2.0 كبروتوكول قياسي (RFC 6749) لتفويض &amp;ldquo;صلاحيات معينة (النطاقات Scope) فقط&amp;rdquo; لـ &amp;ldquo;فترة زمنية محدودة&amp;rdquo; إلى تطبيقات الطرف الثالث، مع تجنب &amp;ldquo;مشاركة كلمات المرور&amp;rdquo; بهذه الطريقة.&lt;/p>
&lt;p>في هذه المقالة، سنشرح آلية عمل OAuth 2.0 بشكل مفصل وعملي للغاية من خلال تنفيذ تطبيق (Slack App) يستهدف &lt;strong>Slack (Slack API)&lt;/strong>، والذي أصبح المعيار الفعلي كأداة تواصل للأعمال. هذا الشرح الشامل الذي يتجاوز 10,000 حرف يتضمن أمثلة برمجية باستخدام Node.js (Express)، ومخططات تسلسلية توضح تدفق البروتوكول، ويتعمق في المفاهيم الأمنية الهامة مثل المعلمة &lt;code>state&lt;/code> والخلفية الرياضية والتشفيرية لـ PKCE.&lt;/p>
&lt;hr>
&lt;h1 id="1-المفاهيم-الأساسية-لـ-oauth-20-الأدوار-الأربعة-roles">1. المفاهيم الأساسية لـ OAuth 2.0: الأدوار الأربعة (Roles)
&lt;/h1>&lt;p>الخطوة الأولى لفهم OAuth 2.0 هي الفهم الدقيق للأشخاص (الأدوار) المعنيين. في RFC 6749، يتم تعريف الأدوار الأربعة التالية:&lt;/p>
&lt;pre class="mermaid">
graph TD
RO[&amp;#34;مالك المورد (المستخدم)&amp;#34;] -- منح التفويض --&amp;gt; C[&amp;#34;العميل (Slack App)&amp;#34;]
C -- طلب التفويض --&amp;gt; AS[&amp;#34;خادم التفويض (خادم تفويض Slack)&amp;#34;]
AS -- إصدار رمز الوصول --&amp;gt; C
C -- الوصول باستخدام الرمز --&amp;gt; RS[&amp;#34;خادم الموارد (خادم Slack API)&amp;#34;]
RO -- تسجيل الدخول بالمعرّف/كلمة المرور --&amp;gt; AS
&lt;/pre>
&lt;ol>
&lt;li>&lt;strong>مالك المورد (Resource Owner)&lt;/strong>
&lt;ul>
&lt;li>الكيان الذي يمتلك صلاحية منح حق الوصول إلى المورد. عادةً ما يشير إلى &amp;ldquo;المستخدم النهائي (الإنسان)&amp;rdquo;. في هذا المثال، هو &amp;ldquo;أنت نفسك، كعضو في مساحة عمل Slack ولديك صلاحية نشر رسائل في القنوات&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>العميل (Client)&lt;/strong>
&lt;ul>
&lt;li>التطبيق الذي يحاول الوصول إلى خادم الموارد بعد الحصول على إذن من مالك المورد. في هذا المثال، هو &amp;ldquo;تطبيق Node.js الذي تقوم بتطويره (Slack App)&amp;rdquo;. على الرغم من تسميته &amp;ldquo;العميل&amp;rdquo;، إلا أنه في سياق OAuth، يسمى &amp;ldquo;العميل&amp;rdquo; حتى لو كان تطبيق ويب يعمل على جانب الخادم.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>خادم التفويض (Authorization Server)&lt;/strong>
&lt;ul>
&lt;li>الخادم الذي يقوم بمصادقة مالك المورد، ويحصل على التفويض منه، ويصدر رمز الوصول للعميل. في هذا المثال، هي بنية مصادقة Slack التي توفر &lt;code>slack.com/oauth/v2/authorize&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>خادم الموارد (Resource Server)&lt;/strong>
&lt;ul>
&lt;li>الخادم الذي يستضيف الموارد المحمية، ويستقبل طلبات الوصول إلى الموارد باستخدام رمز الوصول، ويستجيب لها. في هذا المثال، هي نقاط نهاية &lt;code>slack.com/api/&lt;/code> التي توفر واجهات برمجة التطبيقات مثل &lt;code>chat.postMessage&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>بكلمات بسيطة، تدفق OAuth هو &lt;strong>&amp;ldquo;سلسلة من الخطوات حيث يحصل العميل على موافقة مالك المورد، ويستلم رمز وصول من خادم التفويض، ويستخدمه لاسترداد البيانات والتعامل معها من خادم الموارد&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h1 id="2-التشريح-الكامل-لمنح-رمز-التفويض-authorization-code-grant">2. التشريح الكامل لمنح رمز التفويض (Authorization Code Grant)
&lt;/h1>&lt;p>هناك عدة تدفقات (أنواع المنح) في OAuth 2.0، ولكن التدفق الأكثر توصيةً واستخداماً في البيئات التي يمكن فيها الاحتفاظ بالمفتاح السري (Client Secret) بأمان على جانب الخادم (مثل تطبيقات الويب) هو &lt;strong>منح رمز التفويض (Authorization Code Grant)&lt;/strong>.&lt;/p>
&lt;p>الميزة الرئيسية لمنح رمز التفويض هي الفصل الواضح بين &lt;strong>القناة الأمامية (Front Channel - الاتصال عبر المتصفح)&lt;/strong> و &lt;strong>القناة الخلفية (Back Channel - الاتصال المباشر بين الخوادم)&lt;/strong>. في القناة الأمامية، يتم تمرير &amp;ldquo;رمز تفويض (Authorization Code)&amp;rdquo; مؤقت فقط، بينما يتم الحصول على &amp;ldquo;رمز الوصول (Access Token)&amp;rdquo; النهائي في القناة الخلفية. هذا يقلل بشكل كبير من خطر تسريب الرمز عبر سجل المتصفح أو المُحيل (Referer).&lt;/p>
&lt;p>يوضح المخطط التسلسلي التالي العملية الكاملة لمنح رمز التفويض في Slack App.&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
autonumber
participant U as &amp;#34;مالك المورد (متصفح الويب)&amp;#34;
participant C as &amp;#34;العميل (تطبيق Node.js)&amp;#34;
participant AS as &amp;#34;خادم التفويض (مصادقة Slack)&amp;#34;
participant RS as &amp;#34;خادم الموارد (Slack API)&amp;#34;
U-&amp;gt;&amp;gt;C: &amp;#34;النقر على زر تثبيت التطبيق (GET /slack/install)&amp;#34;
Note over C: &amp;#34;إنشاء المعلمة state&amp;#34;
C--&amp;gt;&amp;gt;U: &amp;#34;إعادة التوجيه: 302 Found (الموقع: عنوان URL لمصادقة Slack)&amp;#34;
U-&amp;gt;&amp;gt;AS: &amp;#34;GET /oauth/v2/authorize?client_id=...&amp;amp;scope=...&amp;amp;state=...&amp;#34;
AS--&amp;gt;&amp;gt;U: &amp;#34;شاشة تسجيل الدخول إلى Slack وشاشة الموافقة على الصلاحيات (Consent Screen)&amp;#34;
U-&amp;gt;&amp;gt;AS: &amp;#34;السماح بالصلاحيات (Allow)&amp;#34;
Note over AS: &amp;#34;إنشاء رمز التفويض (code)&amp;#34;
AS--&amp;gt;&amp;gt;U: &amp;#34;إعادة التوجيه: 302 Found (الموقع: Client Callback URL?code=...&amp;amp;state=...)&amp;#34;
U-&amp;gt;&amp;gt;C: &amp;#34;GET /slack/oauth_redirect?code=...&amp;amp;state=...&amp;#34;
Note over C: &amp;#34;التحقق من المعلمة state (تدبير ضد CSRF)&amp;#34;
C-&amp;gt;&amp;gt;AS: &amp;#34;POST /api/oauth.v2.access (code, client_id, client_secret)&amp;#34;
Note over C,AS: &amp;#34;اتصال القناة الخلفية (لا يمر عبر المتصفح)&amp;#34;
AS--&amp;gt;&amp;gt;C: &amp;#34;200 OK (JSON: إصدار access_token وما إلى ذلك)&amp;#34;
C-&amp;gt;&amp;gt;RS: &amp;#34;POST /api/chat.postMessage (Authorization: Bearer &amp;lt;access_token&amp;gt;)&amp;#34;
RS--&amp;gt;&amp;gt;C: &amp;#34;200 OK (تم نشر الرسالة بنجاح)&amp;#34;
&lt;/pre>
&lt;p>دعونا نحلل هذا التدفق خطوة بخطوة من خلال تنفيذ كود Node.js (Express) محدد.&lt;/p>
&lt;hr>
&lt;h1 id="3-التحضير-للتنفيذ-الإعداد-في-slack-developer-console">3. التحضير للتنفيذ: الإعداد في Slack Developer Console
&lt;/h1>&lt;p>قبل كتابة الكود، نحتاج إلى تسجيل &amp;ldquo;وجود عميل جديد&amp;rdquo; في نظام Slack.&lt;/p>
&lt;ol>
&lt;li>انتقل إلى &lt;a class="link" href="https://api.slack.com/apps" target="_blank" rel="noopener"
>Slack API: Applications&lt;/a> وانقر على &amp;ldquo;Create New App&amp;rdquo;.&lt;/li>
&lt;li>حدد &amp;ldquo;From scratch&amp;rdquo; وحدد اسم التطبيق (مثل: &lt;code>My First OAuth App&lt;/code>) ومساحة العمل التي تريد التثبيت فيها.&lt;/li>
&lt;li>في شاشة &amp;ldquo;Basic Information&amp;rdquo; التي تظهر بعد الإنشاء، احصل على بيانات الاعتماد (Credentials) الهامة التالية.
&lt;ul>
&lt;li>&lt;strong>Client ID&lt;/strong>: المعرّف الذي يميز تطبيقك بشكل فريد وعلني. لا توجد مشكلة في تضمينه في الطلبات التي تمر عبر المتصفح (القناة الأمامية).&lt;/li>
&lt;li>&lt;strong>Client Secret&lt;/strong>: سلسلة سرية يعرفها تطبيقك فقط. &lt;strong>يجب ألا يتم كشفها أبداً لجانب المتصفح، ويجب عدم رفعها (commit) إلى GitHub وغيرها.&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>انتقل إلى شاشة &amp;ldquo;OAuth &amp;amp; Permissions&amp;rdquo; وسجل عنوان URL لرد النداء (Callback) في &amp;ldquo;Redirect URLs&amp;rdquo;. بافتراض التطوير المحلي (Local Development) هذه المرة، سنقوم بإعداد ما يلي:
&lt;ul>
&lt;li>&lt;code>http://localhost:3000/slack/oauth_redirect&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>الآن اكتملت التحضيرات. لنبدأ في تنفيذ الخادم.&lt;/p>
&lt;hr>
&lt;h1 id="4-خطوة-التنفيذ-1-slackinstall-والمعلمة-state-للحماية-من-csrf">4. خطوة التنفيذ 1: &lt;code>/slack/install&lt;/code> والمعلمة &lt;code>state&lt;/code> للحماية من CSRF
&lt;/h1>&lt;p>سنقوم بإنشاء نقطة النهاية الأولى للمستخدمين لبدء استخدام التطبيق (لتثبيته في مساحة العمل). المسؤولية الأكبر هنا هي إعادة توجيه المستخدم إلى خادم تفويض Slack، ولكن الأهمية الأمنية القصوى تكمن في &lt;strong>إنشاء وحفظ المعلمة &lt;code>state&lt;/code>&lt;/strong>.&lt;/p>
&lt;h2 id="الحاجة-إلى-معلمة-state-منع-هجمات-csrf">الحاجة إلى معلمة state (منع هجمات CSRF)
&lt;/h2>&lt;p>إذا لم تكن المعلمة &lt;code>state&lt;/code> موجودة، فيمكن لمهاجم خبيث بدء عملية التفويض باستخدام حساب Slack الخاص به، وجعل الضحية ينقر على عنوان URL لرد النداء الذي يحتوي على &amp;ldquo;رمز التفويض&amp;rdquo; الذي تم الحصول عليه (مثال: &lt;code>http://localhost:3000/slack/oauth_redirect?code=ATTACKER_CODE&lt;/code>). عند قيام متصفح الضحية بتنفيذ ذلك، يكتمل الارتباط بحساب Slack الخاص بالمهاجم في جلسة الضحية، مما يتسبب في تسرب المعلومات أو عمليات غير مقصودة (Login CSRF).&lt;/p>
&lt;p>لمنع ذلك، فإن &lt;code>state&lt;/code> عبارة عن سلسلة عشوائية غير قابلة للتخمين تُستخدم للتحقق من أن المتصفح الذي بدأ الطلب هو نفس المتصفح الذي تلقى رد النداء (Callback).&lt;/p>
&lt;h2 id="إنتروبيا-state-الخلفية-الرياضية">إنتروبيا state (الخلفية الرياضية)
&lt;/h2>&lt;p>لإنشاء &lt;code>state&lt;/code> آمن، يلزم وجود رقم عشوائي يحتوي على &amp;ldquo;إنتروبيا&amp;rdquo; (كمية معلومات) كافية. تعتمد الإنتروبيا $E$ على عدد أنواع السلاسل التي يمكن إنشاؤها $N$، ويتم التعبير عنها بالمعادلة التالية:&lt;/p>
$$
E = \log_2(N) \quad (\text{وحدة: bits})
$$&lt;p>على سبيل المثال، إذا قمنا بإنشاء رقم شبه عشوائي آمن تشفيرياً (CSPRNG) بحجم 16 بايت وتحويله إلى سلسلة سداسية عشرية (Hex)، فسيكون عدد الحالات التي يمكن تمثيلها هو $2^{128}$.&lt;/p>
$$
E = \log_2(2^{128}) = 128 \text{ bits}
$$&lt;p>مع إنتروبيا 128 بت، من المستحيل فعلياً (احتمال فلكي) العثور على تصادم من خلال هجوم القوة العمياء في علوم الكمبيوتر الحديثة. عادةً، يوصى بوجود &lt;code>state&lt;/code> ذو إنتروبيا لا تقل عن 128 بت كمتطلب أمني.&lt;/p>
&lt;h2 id="التنفيذ-باستخدام-nodejs">التنفيذ باستخدام Node.js
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// app.js (مقتطف)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">express&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;express&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">crypto&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;crypto&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">session&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;express-session&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">dotenv&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;dotenv&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">dotenv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">config&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">express&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// إعداد البرنامج الوسيط للجلسة (لحفظ state)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">session&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">secret&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SESSION_SECRET&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resave&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">saveUninitialized&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cookie&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">secure&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// يجب تعيينها كـ true في بيئة الإنتاج
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SLACK_CLIENT_ID&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SLACK_CLIENT_ID&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SLACK_AUTHORIZE_URL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://slack.com/oauth/v2/authorize&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/slack/install&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// إنشاء رقم عشوائي قوي بحجم 16 بايت وتحويله إلى سلسلة سداسية عشرية (الإنتروبيا: 128 بت)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">crypto&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">randomBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;hex&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// حفظ في الجلسة ليتم التحقق منه عند رد النداء (callback)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">session&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">oauth_state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">state&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// قائمة النطاقات (الصلاحيات) المطلوبة (مفصولة بفواصل)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// chat:write = صلاحية إرسال رسالة إلى القناة
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// channels:read = صلاحية قراءة معلومات القنوات العامة
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">scope&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;chat:write,channels:read&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// معلمات URL لبناء خادم تفويض Slack
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">URLSearchParams&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">SLACK_CLIENT_ID&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">scope&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">scope&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">state&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">state&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">redirect_uri&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;http://localhost:3000/slack/oauth_redirect&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">authUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">SLACK_AUTHORIZE_URL&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">?&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// إعادة توجيه المستخدم إلى شاشة تفويض Slack (302 Found)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">redirect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">authUrl&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>عند الوصول إلى نقطة النهاية هذه، ستكون استجابة HTTP كالتالي:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-http" data-lang="http">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">HTTP&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="m">1.1&lt;/span> &lt;span class="m">302&lt;/span> &lt;span class="ne">Found&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Location&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">https://slack.com/oauth/v2/authorize?client_id=123.456&amp;amp;scope=chat%3Awrite%2Cchannels%3Aread&amp;amp;state=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6&amp;amp;redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fslack%2Foauth_redirect&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Set-Cookie&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">connect.sid=...; Path=/; HttpOnly&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>سينتقل متصفح المستخدم على الفور إلى &lt;code>Location&lt;/code> المحدد، وستظهر شاشة Slack (Consent Screen)، وستظهر الشاشة المألوفة &amp;ldquo;My First OAuth App يطلب الوصول إلى مساحة العمل الخاصة بك&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h1 id="5-خطوة-التنفيذ-2-استلام-رد-النداء-واستبدال-رمز-الوصول">5. خطوة التنفيذ 2: استلام رد النداء واستبدال رمز الوصول
&lt;/h1>&lt;p>عندما ينقر المستخدم على &amp;ldquo;السماح (Allow)&amp;rdquo; في شاشة Slack، سيقوم خادم Slack بإعادة توجيه متصفح المستخدم إلى &lt;code>redirect_uri&lt;/code> الذي تم إعداده مسبقاً. في ذلك الوقت، يتم إرفاق &lt;code>code&lt;/code> (رمز التفويض) و &lt;code>state&lt;/code> الذي أرسلناه سابقاً كمعلمات استعلام في عنوان URL.&lt;/p>
&lt;p>في الخلفية (Backend)، نقوم بالعمليات التالية:&lt;/p>
&lt;ol>
&lt;li>التحقق مما إذا كان &lt;code>state&lt;/code> المرسل يتطابق تماماً مع &lt;code>state&lt;/code> المحفوظ في الجلسة.&lt;/li>
&lt;li>في حالة التطابق، استخدام &lt;code>code&lt;/code> المستلم ومعرّف العميل &lt;code>client_id&lt;/code> والمعلومات السرية &lt;code>client_secret&lt;/code> للاتصال بـ Slack API عبر القناة الخلفية وطلب رمز الوصول (Access Token).&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">axios&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;axios&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SLACK_CLIENT_SECRET&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SLACK_CLIENT_SECRET&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">SLACK_ACCESS_TOKEN_URL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://slack.com/api/oauth.v2.access&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/slack/oauth_redirect&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">code&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">state&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">error&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// معالجة حالة رفض المستخدم للتفويض
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;access_denied&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">403&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;تم رفض الوصول.&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 1. التحقق من state (تدبير ضد CSRF)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">savedState&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">session&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">oauth_state&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">state&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">state&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nx">savedState&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">400&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Invalid State Parameter (تم اكتشاف هجوم CSRF)&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// حذف state المستخدم (لمنع هجمات إعادة الإرسال Replay Attack)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">delete&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">session&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">oauth_state&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 2. استبدال رمز التفويض برمز وصول (اتصال القناة الخلفية)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">tokenResponse&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">axios&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">SLACK_ACCESS_TOKEN_URL&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">URLSearchParams&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client_id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">SLACK_CLIENT_ID&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">client_secret&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">SLACK_CLIENT_SECRET&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">code&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">code&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">redirect_uri&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;http://localhost:3000/slack/oauth_redirect&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}).&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/x-www-form-urlencoded&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">tokenResponse&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ok&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Token Exchange Error:&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`Slack API Error: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// نجاح! تم الحصول على رمز الوصول
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">accessToken&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">access_token&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">teamName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">team&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">botUserId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">bot_user_id&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`Successfully installed to &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">teamName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">. Access Token: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">accessToken&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// عادةً ما تقوم بتشفير الرمز وحفظه في قاعدة البيانات هنا
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// saveToDatabase(data.team.id, encrypt(accessToken));
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`اكتمل التثبيت! مساحة العمل: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">teamName&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Network Error:&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;حدث خطأ في الاتصال.&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>كاستجابة لـ &lt;code>/api/oauth.v2.access&lt;/code>، سيُرجع Slack ملف JSON كما يلي.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ok&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;app_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;A12345678&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;authed_user&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;U12345678&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;scope&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;chat:write,channels:read&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;token_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;bot&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;access_token&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;YOUR_BOT_TOKEN_HERE&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;bot_user_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;B12345678&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;team&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;T12345678&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;My Workspace&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;enterprise&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>هذه السلسلة التي تبدأ بـ &lt;code>xoxb-&lt;/code> هي &lt;strong>رمز وصول البوت (Bot Access Token)&lt;/strong> في Slack. من الآن فصاعداً، عندما يرسل التطبيق طلباً إلى Slack API (خادم الموارد)، سيتم إثبات المصادقة والصلاحيات عن طريق إضافة &lt;code>Authorization: Bearer xoxb-...&lt;/code> إلى ترويسة HTTP.&lt;/p>
&lt;hr>
&lt;h1 id="6-نطاقات-الرموز-ومبدأ-الامتياز-الأقل-principle-of-least-privilege">6. نطاقات الرموز ومبدأ الامتياز الأقل (Principle of Least Privilege)
&lt;/h1>&lt;p>أحد أهم المفاهيم في OAuth 2.0 هو &amp;ldquo;النطاق (Scope)&amp;rdquo;. يشير النطاق إلى مدى الصلاحيات المرتبطة برمز الوصول.&lt;/p>
&lt;p>في Slack، يتم تصنيف الصلاحيات بدقة بالغة، وتنقسم بشكل عام إلى &lt;strong>نطاقات رموز البوت (Bot Token Scopes)&lt;/strong> و &lt;strong>نطاقات رموز المستخدمين (User Token Scopes)&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>&lt;code>chat:write&lt;/code> (Bot): صلاحية إرسال رسالة إلى القناة كالتطبيق (البوت) نفسه.&lt;/li>
&lt;li>&lt;code>chat:write&lt;/code> (User): صلاحية إرسال رسالة نيابة عن المستخدم الذي قام بتثبيت التطبيق (باسم المستخدم وأيقونته).&lt;/li>
&lt;li>&lt;code>channels:read&lt;/code>: صلاحية الحصول على قائمة القنوات.&lt;/li>
&lt;li>&lt;code>channels:history&lt;/code>: صلاحية قراءة سجل الرسائل السابقة في القناة.&lt;/li>
&lt;/ul>
&lt;p>وفقاً للمبدأ الأمني الأساسي &amp;ldquo;مبدأ الامتياز الأقل (Principle of Least Privilege)&amp;quot;، فإن القاعدة الذهبية هي &lt;strong>طلب النطاقات الضرورية تماماً فقط للوظائف التي يوفرها التطبيق&lt;/strong>. على سبيل المثال، إذا كان التطبيق مخصصاً &amp;ldquo;لإرسال الإشعارات فقط&amp;rdquo;، فيجب أن يطلب &lt;code>chat:write&lt;/code> فقط، ولا ينبغي له طلب &lt;code>channels:history&lt;/code> (صلاحية قراءة جميع المحادثات السابقة). هذا لتقليل الضرر إلى الحد الأدنى في الحالة غير المتوقعة التي يتم فيها اختراق التطبيق وتسريب الرمز.&lt;/p>
&lt;hr>
&lt;h1 id="7-أمان-متقدم-أكثر-pkce-مفتاح-الإثبات-لتبادل-الرمز-proof-key-for-code-exchange">7. أمان متقدم أكثر: PKCE (مفتاح الإثبات لتبادل الرمز Proof Key for Code Exchange)
&lt;/h1>&lt;p>مؤخراً، تم توحيد &lt;strong>PKCE (مفتاح الإثبات لتبادل الرمز، RFC 7636، يُنطق &amp;ldquo;بيكسي&amp;rdquo;)&lt;/strong> واستخدامه على نطاق واسع كآلية لتعزيز أمان OAuth 2.0.&lt;/p>
&lt;p>في الأصل، تم تصميم PKCE لـ &amp;ldquo;العملاء العامين (Public Clients)&amp;rdquo; مثل التطبيقات الأصلية (iOS/Android) أو تطبيقات الصفحة الواحدة (SPA) التي لا يمكنها الاحتفاظ بـ &lt;code>client_secret&lt;/code> بأمان. ومع ذلك، يوصى الآن بشدة باستخدام PKCE حتى مع &amp;ldquo;العملاء السريين (Confidential Clients)&amp;rdquo; على جانب الخادم وفقاً لأفضل الممارسات الأمنية (مسودة OAuth 2.1).&lt;/p>
&lt;h2 id="آلية-عمل-pkce-والخلفية-الرياضية">آلية عمل PKCE والخلفية الرياضية
&lt;/h2>&lt;p>يُثبت PKCE بشكل مشفر أن &amp;ldquo;الشخص الذي بدأ طلب التفويض&amp;rdquo; و &amp;ldquo;الشخص الذي يطلب استبدال الرمز&amp;rdquo; هما نفس الكيان.&lt;/p>
&lt;ol>
&lt;li>يُنشئ العميل سلسلة عشوائية &lt;strong>&lt;code>code_verifier&lt;/code>&lt;/strong> (من 43 إلى 128 حرفاً).&lt;/li>
&lt;li>يقوم بعمل تجزئة (Hash) لها باستخدام &lt;strong>SHA-256&lt;/strong>، وتشفيرها بتنسيق BASE64URL، ليصبح الناتج هو &lt;strong>&lt;code>code_challenge&lt;/code>&lt;/strong>.&lt;/li>
&lt;/ol>
&lt;p>بصيغة رياضية، يمكن التعبير عنها كما يلي:&lt;/p>
$$
\text{code\_challenge} = \text{BASE64URL-ENCODE}( \text{SHA256}( \text{ASCII}(\text{code\_verifier}) ) )
$$&lt;ol start="3">
&lt;li>يقوم العميل بإرسال &lt;code>code_challenge&lt;/code> و &lt;code>code_challenge_method=S256&lt;/code> بالإضافة إلى &lt;code>state&lt;/code> إلى خادم التفويض (Slack) عند تنفيذ &lt;code>/slack/install&lt;/code> (يقوم Slack بحفظ هذا مؤقتاً).&lt;/li>
&lt;li>بعد رد النداء (Callback)، عند استبدال الرمز (&lt;code>/api/oauth.v2.access&lt;/code>)، يقوم العميل بإرسال &lt;strong>&lt;code>code_verifier&lt;/code>&lt;/strong> الأصلي قبل عملية التجزئة.&lt;/li>
&lt;li>يقوم خادم التفويض (Slack) بعمل تجزئة SHA-256 لـ &lt;code>code_verifier&lt;/code> المستلم بنفسه، ويتحقق مما إذا كان يتطابق تماماً مع &lt;code>code_challenge&lt;/code> المحفوظ في الخطوة 3.&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
sequenceDiagram
participant C as &amp;#34;العميل (Client)&amp;#34;
participant AS as &amp;#34;خادم التفويض (Authorization Server)&amp;#34;
Note over C: &amp;#34;code_verifier = سلسلة عشوائية&amp;#34;&amp;lt;br/&amp;gt;&amp;#34;code_challenge = SHA256(code_verifier)&amp;#34;
C-&amp;gt;&amp;gt;AS: &amp;#34;طلب التفويض (إرسال code_challenge)&amp;#34;
Note over AS: &amp;#34;الاحتفاظ بـ code_challenge&amp;#34;
AS--&amp;gt;&amp;gt;C: &amp;#34;إصدار رمز التفويض (code)&amp;#34;
C-&amp;gt;&amp;gt;AS: &amp;#34;طلب الرمز (إرسال code + code_verifier)&amp;#34;
Note over AS: &amp;#34;SHA256(المُحقق المستلم) == التحدي المحفوظ؟&amp;#34;
AS--&amp;gt;&amp;gt;C: &amp;#34;نجاح التحقق: إصدار رمز الوصول&amp;#34;
&lt;/pre>
&lt;p>من خلال هذه الآلية، حتى لو تم سرقة &amp;ldquo;رمز التفويض (code)&amp;rdquo; بواسطة تطبيق خبيث أو عن طريق التنصت على مسار الاتصال، لا يمكن للمهاجم الحصول على رمز الوصول لأنه لا يعرف &lt;code>code_verifier&lt;/code> الأصلي (نظراً لطبيعة دالة التجزئة SHA-256 غير القابلة للعكس، من المستحيل حساب الـ verifier العكسي من التحدي challenge).&lt;/p>
&lt;p>حالياً، تتقدم دعم PKCE في بعض التدفقات الجديدة لـ Slack API وغيرها من واجهات برمجة تطبيقات SaaS الحديثة (مثل Auth0، Okta، X/Twitter API v2 وغيرها)، وأصبح تقنية يجب على المطورين اعتمادها بنشاط.&lt;/p>
&lt;hr>
&lt;h1 id="8-الإدارة-والتشغيل-الآمن-لرموز-الوصول">8. الإدارة والتشغيل الآمن لرموز الوصول
&lt;/h1>&lt;p>أخيراً، إليك أفضل الممارسات لكيفية حفظ رموز الوصول التي تم الحصول عليها.&lt;/p>
&lt;h2 id="1-التشفير-الإلزامي-عند-الحفظ-في-قاعدة-البيانات">1. التشفير الإلزامي عند الحفظ في قاعدة البيانات
&lt;/h2>&lt;p>رمز الوصول (&lt;code>xoxb-...&lt;/code>) هو حرفياً &amp;ldquo;المفتاح الرئيسي&amp;rdquo; لمساحة عمل Slack. يجب ألا تحفظه بنص صريح في قاعدة البيانات (مثل MySQL، PostgreSQL، MongoDB). في حالة حدوث كارثة مثل تسرب قاعدة البيانات عبر حقن SQL مثلاً، سيتم اختراق مساحات عمل Slack لجميع العملاء.&lt;/p>
&lt;p>تأكد دائماً من تشفيره باستخدام تشفير قوي بالمفتاح المتماثل مثل &lt;strong>AES-256-GCM&lt;/strong> في طبقة التطبيق قبل حفظه في قاعدة البيانات (DB). تتم إدارة المفتاح الرئيسي للتشفير/فك التشفير بصرامة باستخدام خدمات إدارة المفاتيح الآمنة مثل AWS KMS (Key Management Service) أو GCP Cloud KMS.&lt;/p>
&lt;h2 id="2-تدوير-الرموز-token-rotation">2. تدوير الرموز (Token Rotation)
&lt;/h2>&lt;p>هناك مخاطرة في الاستمرار في استخدام رموز صالحة لفترة طويلة. في أحدث تطبيقات OAuth، يوصى باعتماد آلية لإنشاء رمز وصول جديد كل بضع ساعات باستخدام &amp;ldquo;رمز التجديد (Refresh Token)&amp;rdquo; وهو ما يسمى بـ (Token Rotation). في Slack API أيضاً، يمكنك تفعيل تدوير الرموز (Token Rotation) في الإعدادات الاختيارية.&lt;/p>
&lt;hr>
&lt;h1 id="الخلاصة">الخلاصة
&lt;/h1>&lt;p>في هذه المقالة، شرحنا بالتفصيل تدفق منح رمز التفويض لـ OAuth 2.0، مع أمثلة برمجية محددة لتنفيذ تكامل Slack App باستخدام Node.js.&lt;/p>
&lt;ol>
&lt;li>من خلال الوعي بـ &lt;strong>الأدوار الأربعة (RO، Client، AS، RS)&lt;/strong>، تصبح بنية النظام بأكمله واضحة.&lt;/li>
&lt;li>&lt;strong>منح رمز التفويض&lt;/strong> يضمن الأمان من خلال الاستخدام الذكي لمسارات الاتصال (القناة الأمامية/الخلفية) بين المتصفح والخادم.&lt;/li>
&lt;li>إن فهم الآليات التشفيرية الأساسية، مثل الدفاع ضد CSRF بواسطة &lt;strong>المعلمة &lt;code>state&lt;/code>&lt;/strong> ومنع هجمات اعتراض رمز التفويض بواسطة &lt;strong>PKCE&lt;/strong>، هو أقصر طريق للتنفيذ الآمن.&lt;/li>
&lt;li>يعتبر تصميم النطاق بناءً على &lt;strong>مبدأ الامتياز الأقل&lt;/strong> والتشفير عند الحفظ في قاعدة البيانات عناصر لا غنى عنها مطلقاً في العمليات التشغيلية.&lt;/li>
&lt;/ol>
&lt;p>عالم OAuth 2.0 عميق جداً، وهناك مواصفات ضخمة في RFC وحده، ولكن من خلال التعلم العملي باستهداف منصة فعلية (Slack) كما فعلنا، ستتمكن من إدراك فلسفة التصميم المتطورة وآليات الأمان القوية الخاصة به. نأمل أن تكون المعرفة الواردة في هذه المقالة مفيدة في تطوير تطبيقاتك المستقبلية وتنفيذ تكامل واجهات برمجة التطبيقات (API).&lt;/p></description></item></channel></rss>