لطالما كان تدفق رمز التفويض هو المعيار الذهبي لتأمين تطبيقات الويب. ومع ذلك، قدمت تطبيقات الصفحة الواحدة (SPAs) والعملاء المتنقلين الأصليين تحدياً فريداً: فهي "عملاء عامون". على عكس تطبيقات الخوادم التقليدية، لا يمكن لهذه العملاء تخزين سر العميل بشكل آمن. هذا الثغرة فتحت الباب أمام الجهات الخبيثة لاعتراض رموز التفويض والتظاهر بمستخدمين شرعيين، وهو تهديد يُعرف باعتراض رمز التفويض.
كان رد الصناعة هو إدخال PKCE (مفتاح إثبات لتبادل الرموز)، المحدد في RFC 7636. تضيف PKCE طبقة من الأمان دون الحاجة إلى سر العميل، مما يجعلها المعيار الإلزامي لـ OAuth2 في العملاء العامون. ستوجه هذه المقالة المطورين من المستوى المتوسط إلى المتقدم خلال ميكانيكا PKCE وكيفية تنفيذها بأمان.
لماذا يعد PKCE أمراً لا غنى عنه للعملاء العامون
قبل الغوص في الكود، من الضروري فهم نموذج التهديد. في تدفق رمز التفويض القياسي بدون PKCE، يبدو التدفق كالتالي:
- يعيد العميل توجيه المستخدم إلى خادم التفويض.
- يقوم المستخدم بالمصادقة ومنح الإذن.
- يعيد خادم التفويض التوجيه إلى العميل مع رمز تفويض.
- يتبادل العميل الرمز للحصول على رمز وصول باستخدام معرف العميل وسره.
في تطبيق SPA أو تطبيق متنقل، لا يوجد خادم خلفي للاحتفاظ بالسر. إذا تمكن مهاجم من اعتراض رمز التفويض (عبر XSS، أو البرمجيات الخبيثة، أو التنصت على الشبكة)، فيمكنه استخدام هذا الرمز لطلب رمز وصول، مما يؤدي فعلياً إلى اختطاف جلسة المستخدم. تحل PKCE هذه المشكلة عن طريق ربط رمز التفويض بمفتاح تشفيري يعرفه العميل فقط في وقت الطلب الأولي.
شرح سير عمل PKCE
يُقدم تدفق PKCE مكونين رئيسيين: code_verifier و code_challenge.
- مدقق الرمز (Code Verifier): سلسلة عشوائية تشفيرية عالية الانتروبيا يتم إنشاؤها بواسطة العميل.
- تحدي الرمز (Code Challenge): نسخة محوّلة من المدقق، تُرسل إلى خادم التفويض أثناء طلب تسجيل الدخول الأولي.
عندما يتبادل العميل الرمز للحصول على رمز لاحقاً، يرسل code_verifier الأصلي. يقوم خادم التفويض بتحويل هذا المدقق ومقارنته بـ code_challenge الذي تلقاه سابقاً. إذا تطابقا، يتم إصدار الرمز.
التنفيذ: إنشاء مدقق الرمز وتحديه
الطريقة الأكثر أماناً لإنشاء التحدي هي استخدام تحويل S256. يتضمن ذلك أخذ تجزئة SHA-256 لمدقق الرمز وتشفير النتيجة باستخدام Base64 الآمن للروابط.
إليك تنفيذ عملي بلغة JavaScript لإنشاء هذه القيم في بيئة المتصفح:
function generateRandomString(length) {
const array = new Uint32Array(length / 2);
window.crypto.getRandomValues(array);
return Array.from(array, dec => ('0' + dec.toString(16)).slice(-2)).join('');
}
async function createVerifier() {
// Generate a random string between 43 and 128 characters
const verifier = generateRandomString(32);
return verifier;
}
async function createChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await window.crypto.subtle.digest('SHA-256', data);
// Convert to Base64 URL Encoded string
const base64encoded = btoa(String.fromCharCode(...new Uint8Array(hash)));
return base64encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
تنفيذ طلب التفويض
بمجرد الحصول على code_verifier و code_challenge، يجب إعادة توجيه المستخدم إلى خادم التفويض. الإضافة الحاسمة هنا هي معلمتي code_challenge و code_challenge_method=S256.
const params = new URLSearchParams({
client_id: 'YOUR_CLIENT_ID',
redirect_uri: 'http://localhost:3000/callback',
response_type: 'code',
scope: 'openid profile email',
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
window.location.href = `https://auth-server.com/authorize?${params}`;
معالجة تبادل الرموز
بعد منح المستخدم للإذن، يتم إعادة توجيهه مرة أخرى إلى redirect_uri الخاص بك مع رمز تفويض في سلسلة استعلام URL. الخطوة الأخيرة هي تبادل هذا الرمز للحصول على رمز وصول. والأهم من ذلك، يجب إرسال code_verifier الذي أنشأته في بداية التدفق.
const tokenResponse = await fetch('https://auth-server.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code: authorizationCode, // From URL query params
redirect_uri: 'http://localhost:3000/callback',
client_id: 'YOUR_CLIENT_ID',
code_verifier: codeVerifier // The original random string
})
});
const tokens = await tokenResponse.json();
الخاتمة
لم يعد تنفيذ PKCE اختيارياً للمطورين الذين يبنون تطبيقات SPA أو تطبيقات متنقلة. من خلال إضافة بضعة أسطر من المنطق التشفيري فقط، تحمي مستخدميك من هجمات اعتراض رمز التفويض. بينما يتطلب الإعداد الأولي إدارة دقيقة للحالة وتخزين آمناً لـ code_verifier، فإن الفوائد طويلة المدى لأمان التطبيق كبيرة. مع انتقال النظام البيئي نحو معايير أمان أكثر صرامة، يضمن اعتماد PKCE بقاء تطبيقاتك قوية ومتوافقة وموثوقة من قبل المستخدمين.