مُتَّسِعات OAuth 2.0 رمز التصاريح، PKCE، وبيانات العميل

تحديث في

دليل المطورين في اختيار التدفق المناسب لـ OAuth 2.0. يغطي تدفق التصاريح (للتطبيقات الويب)، وPKCE (للاستمارات والهواتف)، وبيانات العميل (للاستجابة بين الخوادم) مع أمثلة على الكود المُطبقة وأخطاء تُصيبك لاحقًا.

مُتَّسِعات OAuth 2.0: التصاريح، PKCE، وبيانات العميل 1
إعلان · حذف؟

تغطي ثلاث مُتَّسِعات 95% من حالات استخدام OAuth 2.0 الحقيقية. يُعرّف المعيار أكثر، لكن باقيها مُستَبعدة، أو حالة حدودية، أو كلاهما. اختر المُتَّسِع المناسب في البداية، وستُجنبك تغييرات مُلَّفّة في التصميم عند تغيير متطلبات التحقق من الهوية.

مُتَّسِعمتى تستخدمهاهل يشمل المستخدم؟هل يلزم سرّ العميل؟
مُتَّسِع التصاريح (Authorization Code)تطبيق ويب مع خادم خلفينعمنعم — يبقى على الخادم
مُتَّسِع التصاريح + PKCEتطبيق ويب مُبسط، تطبيق مُحمول، أي عميل علنينعملا
مُتَّسِع بيانات العميلتوصيل بين الخوادم (بدون مستخدم)لانعم — يبقى على الخادم

مُتَّسِع التصاريح

السَّيْر القياسي لتطبيقات الويب مع خادم. لا يُلامس بطاقة الوصول أو سر العميل المتصفح — يتم تبادل البطاقة على الخادم. وهذا هو الهدف كليًا.

الخطوة 1: اتجه المستخدم

ابنِ رابط تأكيد واتجه المتصفح إليه:

GET https://accounts.example.com/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=openid+profile+email
  &state=RANDOM_CSRF_TOKEN

ال state القيمة هي وسيلة دفاعك ضد التوجيه. أنشئ سلسلة عشوائية جديدة لكل مُتَّسِع، احفظها في الجلسة، وتحقق منها عند عودة المستخدم. إذا تجاهلت هذه المراجعة، يمكن للاختراق أن يوجه المستخدم من خلال مسار التصاريح باستخدام تصاريحه الخاصة — وينتج توصيل حساب المستخدم إلى هوية المهاجم.

الخطوة 2: إدارة التوجيه العائد

يُوجّه الخادم إلى redirect_uri بصيغة رمز قصير:

GET https://yourapp.com/callback?code=AUTH_CODE&state=SAME_STATE_YOU_SENT

تحقق state يُطابق ما حفظته. ثم يتم تبادل الرمز على الخادم.

الخطوة 3: تبادل الرمز للبطاقات (مُسموح فقط على الخادم)

POST https://accounts.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

الرد:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200a12b3c...",
  "scope": "openid profile email"
}

احتفظ بالبطاقتين على الخادم. عندما access_token يُنتهي (تحقق من expires_in)، استخدم refresh_token لإعادة الحصول على بطاقة جديدة دون أن يُطلب من المستخدم التسجيل مرة أخرى.

PKCE — للاستخدامات التي لا يمكنها الحفاظ على السرّ

يُفترض أن تطبيقات ويب مبسطة أو تطبيقات مُحمولة لا تمتلك مكانًا آمنًا لحفظ client_secret. يمكن لأي شخص فتح أدوات التصميم وتحديد القيمة في ملفك JavaScript. يمكن لأي شخص تحليل ملف APK. يحل PKCE (مُختبر مُستند على الرمز، يُنطق "pixy") هذه المشكلة باستخدام تحدي تشفير مُستند على مرة واحدة — لا حاجة لسر مشترك.

السَّيْر متماثل مع مُتَّسِع التصاريح مع إضافة علتين: سلسلة code_verifier (سلسلة عشوائية تُنشأ) وسلسلة code_challenge (مُختصرة SHA-256 للتحقق، مُختصرة بـ base64url). تُرسل التحدي مبكرًا، ثم تُثبت أنك تمتلك التحقق عند تبادل الرمز.

الخطوة 1: أنشئ التحقق والتحدي

function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64url(array);
}

async function generateCodeChallenge(verifier) {
  const data = new TextEncoder().encode(verifier);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return base64url(new Uint8Array(digest));
}

function base64url(buffer) {
  return btoa(String.fromCharCode(...buffer))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}

// Usage
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store codeVerifier in memory — NOT localStorage

احتفظ بالـ code_verifier في الذاكرة — متغير مُحدد على مستوى الوحدة، وليس في localStorage. سترسله عند تبادل الرمز.

الخطوة 2: طلب التأكيد — أضف التحدي

GET https://accounts.example.com/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=openid+profile
  &state=RANDOM_CSRF_TOKEN
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256

الخطوة 3: تبادل الرمز — بدل سر العميل

POST https://accounts.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&code_verifier=ORIGINAL_VERIFIER_YOU_GENERATED

يُختصر الخادم التحقق ويُقارن مع التحدي الذي أرسلته في الخطوة 2. لا يمكن للاختراق الذي استلم الرمز أن يُعرف ما هو التحقق — لا يمكنه تبادله.

مهم أن تعرف: OAuth 2.1 (التحديث الحديث لـ 2.0) يُلزم PKCE لكل السَّيْر التي تشمل التوجيه. إذا كنت تكتب كودًا جديدًا، استخدم PKCE بغض النظر عن ما إذا طلبته مزود الخدمة حاليًا.

مُتَّسِع بيانات العميل — لا يُحتاج إلى مستخدم، لا مشكلة

مهمات الخلفية، خدمات ميكرو مُتصل بخدمات ميكرو، مهام تكرارية تصل إلى واجهة برمجة تطبيقات — لا تشمل أي من هذه الحالات مستخدمًا. مُتَّسِع بيانات العميل هو السبيل الصحيح: يُحقق الخدمة مباشرة باستخدام هوية العميل وسره.

POST https://accounts.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=api:read api:write

الرد:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 86400
}

لا يوجد بطاقة تجديد — عندما تنتهي، فقط طلب بطاقة جديدة. الخطأ الشائع: إرسال طلب للبطاقة في كل مكالمة API. احفظ البطاقة، وتحقق من انتهاء المدة قبل كل طلب، وقم بطلب تجديد فقط عندما تقترب من انتهاء المدة. طلب بطاقة مرة واحدة في اليوم (أو في الساعة، حسب expires_in) بدلًا من طلب واحد لكل طلب:

let cachedToken = null;
let tokenExpiresAt = 0;

async function getAccessToken() {
  // Refresh 30 seconds before actual expiry
  if (cachedToken && Date.now() < tokenExpiresAt - 30_000) {
    return cachedToken;
  }

  const res = await fetch('https://accounts.example.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: process.env.CLIENT_ID,
      client_secret: process.env.CLIENT_SECRET,
      scope: 'api:read api:write',
    }),
  });

  const data = await res.json();
  cachedToken = data.access_token;
  tokenExpiresAt = Date.now() + (data.expires_in * 1000);
  return cachedToken;
}

أخطاء المطورين التي يُحدثونها فعليًا

حفظ البطاقات في localStorage

أي ثغرة XSS — في كودك الخاص، أو في مكوناتك، أو في مُدير العلامات — يمكن أن يقرأ كل شيء في localStorage. بالنسبة للتطبيقات المبسطة: احفظ بطاقة الوصول في الذاكرة (متغير مُحدد على مستوى الوحدة الذي يختفي مع التحديث). استخدم أكواد httpOnly للبطاقات المُجددية عندما يكون لديك خادم يمكنه تعيينها. لا يمكن للجافاسكربت قراءة أكواد httpOnly.

استخدام مُتَّسِع التصاريح المُبسط

يُرجع مُتَّسِع التصاريح البطاقات مباشرة في قسم URL (#access_token=...). تنتهي هذه البطاقات في تاريخ المتصفح، سجلات الخادم، وعناوين الاتجاه. تم إلغاؤها في RFC 9700. لا يوجد سبب لاستخدام مُتَّسِع التصاريح المُبسط في الكود الجديد. استخدم PKCE.

إهمال التحقق من الحالة

تنزيل كـ .yml state التحقق على التوجيه العائد، يمكن للاختراق أن يُصمم رابط توجيه يكمل مُتَّسِع التصاريح باستخدام تصاريحه الخاصة. النتيجة: يتم ربط حساب المستخدم بهوية المهاجم في المزود. أنشئه لكل مُتَّسِع، احفظه في الجلسة، وتحقق منه عند العودة.

وضع سر العميل في كود الواجهة الأمامية

لا يوجد شيء يُسمى سرًا يعيش في المتصفح. لا يُخفيه التقليل. لا يحميه التحويل. إذا كان بيئة التشغيل هي المتصفح أو تطبيق مُحمول، فإنك تمتلك عميل علني — استخدم PKCE وافرِض سر العميل بالكامل. هذا ليس تدبيرًا؛ هذا هو الطريقة التي يُفترض أن يعمل بها العميل العلني.

عدم التعامل مع انتهاء البطاقة بشكل مبكر

كل بطاقة الوصول لها قيمة expires_in . إذا كنت تُحتفظ بها حتى تفشل بـ 401 ثم تُعيد التحقق، فإن المستخدمين يواجهون أخطاء غامضة. تحقق من انتهاء المدة قبل إرسال الطلب، وقم بتحديث البطاقة مبكرًا (مثلاً 30 ثانية قبل انتهاء المدة)، وقم بمعالجة الحالة النادرة التي تنتهي فيها بطاقة التحديث.

فحص البطاقات أثناء العمل

تُصدر معظم مزودي OAuth بطاقات JWT كبطاقات وصول. يُمكن قراءة المحتوى دون مفتاح السر — فقط التوقيع يحتاج إلى المفتاح لتأكيد. عند تدقيق سيرتك ورغبتك في رؤية المطالبات، أو نطاقات الوصول، أو انتهاء المدة في البطاقة، أدخلها إلى فك ترميز JWT.

إذا كنت تُختبر PKCE وتحتاج إلى التحقق من ما يُستخلص من التشفير base64url المُختصر، فإن code_challenge يُمكن تحليله إلى مُحول base64 يُعالج الأنواع القياسية والآمنة للرابط.

عندما يهم الموضع الفيزيائي للمفتاح أكثر من الحرف (أمثلة: التحكم في الألعاب، أو المفاتيح المبنية على الموضع).

سؤال واحد يحدد المُتَّسِع الصحيح: هل يمتلك بيئة التشغيل مكانًا آمنًا لحفظ السر؟

  • خادم خلفي → مُتَّسِع التصاريح أو بيانات العميل (يُبقى السر على الخادم)
  • متصفح أو تطبيق مُحمول → مُتَّسِع التصاريح + PKCE (لا يوجد سر إطلاقًا)
  • لا يُشارك فيه مستخدم → بيانات العميل

يُمكن أن يعمل PKCE في كل السَّيْر التي تشمل التوجيه — لا توجد عيوب في استخدامه حتى لو لم يطلب مزود الخدمة ذلك بعد. سينتظم ذلك في OAuth 2.1.

هل تريد حذف الإعلانات؟ تخلص من الإعلانات اليوم

تثبيت ملحقاتنا

أضف أدوات IO إلى متصفحك المفضل للوصول الفوري والبحث بشكل أسرع

أضف لـ إضافة كروم أضف لـ امتداد الحافة أضف لـ إضافة فايرفوكس أضف لـ ملحق الأوبرا

وصلت لوحة النتائج!

لوحة النتائج هي طريقة ممتعة لتتبع ألعابك، يتم تخزين جميع البيانات في متصفحك. المزيد من الميزات قريبا!

إعلان · حذف؟
إعلان · حذف؟
إعلان · حذف؟

ركن الأخبار مع أبرز التقنيات

شارك

ساعدنا على الاستمرار في تقديم أدوات مجانية قيمة

اشتري لي قهوة
إعلان · حذف؟