طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 243 · أمن الويب
الدرس 17 · الوحدة 8: تقنية OpenID Connect

OpenID Connect و id_token

كيف يثبت لك «الدخول بـ Google» هوية المستخدم في رمز موقّع؟ ⏱ نحو 45 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح ما يضيفه OIDC فوق OAuth.
  • تصف id_token ومطالباته.
  • تتحقق من JWT بالكامل قبل الوثوق به.
  • تعرف دور nonce و userinfo.

١من التفويض إلى الهوية

OAuth يجيب «ماذا يُسمح لتطبيقك»، لكنه لا يقول من المستخدم بطريقة موحّدة. OpenID Connect (OIDC) طبقة رقيقة فوق OAuth تضيف الهوية: بالتدفق نفسه (Authorization Code + PKCE)، لكن مع نتيجة إضافية هي id_token يصف المستخدم بصيغة قياسية.

OAuth 2.0OpenID Connect
يجيبماذا يُسمح؟ (تفويض)من المستخدم؟ (هوية)
النتيجةaccess tokenaccess token + id_token
الاستخدامالوصول إلى واجهات«تسجيل الدخول بـ…»

هذا ما خلف أزرار «سجّل الدخول بـ Google/Apple»: OIDC يسلّمك هوية المستخدم موثّقة دون أن تدير أنت كلمات المرور.

٢ما id_token؟ إنه JWT

id_token هو JWT (رمز ويب JSON): ثلاثة أجزاء مفصولة بنقاط — ترويسة، وحمولة (المطالبات)، وتوقيع — كلها مُرمّزة base64url:

الترويسة
alg typ

.

الحمولة
sub name exp

.

التوقيع
HMAC/RSA

أجزاء JWT الثلاثة؛ التوقيع يحمي الترويسة والحمولة من العبث.
المطالبة Claimالمعنى
subمعرّف المستخدم الفريد (الأهم للهوية)
issالمُصدِر (خادم الهوية)
audالجمهور (تطبيقك المقصود)
expوقت الانتهاء
nonceقيمة تربط الرمز بطلب دخولك (ضد إعادة التشغيل)

انظر خادمًا يصدر id_token ثم يتحقق منه. التوقيع يضمن أن الحمولة لم تُعبث، والتحقق يكشف أي تلاعب:

const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const jwt = require('jsonwebtoken');const SECRET = 'oidc-secret'; app.get('/callback', (req, res) => {  // خادم الهوية يصدر id_token موقّعًا يصف المستخدم  const idToken = jwt.sign(    { sub: 'user-42', name: 'سارة', email: 'sara@mail.sa', iss: 'https://id.example', aud: 'rafee-store' },    SECRET, { expiresIn: '1h', noTimestamp: true });  res.json({ idToken });}); app.post('/verify', (req, res) => {  try {    const claims = jwt.verify(req.body.idToken, SECRET);   // يتحقق من التوقيع    res.json({ user: claims.sub, name: claims.name });  } catch (e) {    res.status(401).json({ error: 'id_token غير صالح' });  }}); app.listen(3000);

الخطوة الأولى تصدر id_token يصف سارة. والتحقق من رمز مزوّر يفشل 401؛ لأن توقيعه لا يطابق. المطالبات موثوقة فقط بعد التحقق من التوقيع.

٣لا تثق قبل أن تتحقق

قراءة محتوى JWT سهلة (base64url غير مشفّر)، فلا يكفي فك الرمز؛ يجب التحقق منه كاملًا قبل الوثوق بمطالباته:

ونقطة userinfo في OIDC تعطي تفاصيل أكثر عن المستخدم مقابل رمز الوصول، حين تحتاج أكثر مما في id_token.

توقّع وتحقّق

OIDC يضيف فوق OAuth:

id_token هو:

المطالبة التي تحمل معرّف المستخدم:

قبل الوثوق بمطالبات JWT يجب:

الخلاصة

تحدٍّ برمجي: تحقق من id_token

متقدم

نقطة التحقق تقرأ مطالبات id_token بفكّه (decode) فقط دون التحقق من التوقيع، فيقبل أي رمج مزوّر. أصلحها لتتحقق من التوقيع، وترفض المزوّر.

المطلوب:
  • استخدم jwt.verify بالمفتاح بدل jwt.decode.
  • الرمز الصحيح يعيد اسم المستخدم.
  • الرمز المزوّر يُرفض 401.
👀 اعرض الاستجابات المطلوبة
✓ التحقق الآلي: يشغّل المختبر خادمك من جديد ويرسل إليه هذه الطلبات بالترتيب ثم يفحص الردود
  1. GET /token
  2. POST /me {"idToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJoYWNrZXIiLCJuYW1lIjoi2YXYrdin2KzZhSJ9.X"}
  • الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
  • الرمز المزوّر يُرفض ← 401
  • يستخدم jwt.verify
  • لا يعتمد على jwt.decode للتحقق

💡 تلميح 1
بدّل jwt.decode بـ jwt.verify(token, SECRET) داخل try/catch.
💡 تلميح 2
verify يرمي استثناءً إن كان التوقيع خاطئًا؛ التقطه وردّ 401.
💡 تلميح 3
decode يقرأ بلا تحقق، فلا يصلح للثقة.

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const jwt = require('jsonwebtoken');const SECRET = 'oidc-secret';app.get('/token', (req, res) =>  res.json({ idToken: jwt.sign({ sub: 'u1', name: 'سارة' }, SECRET, { noTimestamp: true }) }));app.post('/me', (req, res) => {  try {    const claims = jwt.verify(req.body.idToken, SECRET);    res.json({ name: claims.name });  } catch (e) {    res.status(401).json({ error: 'غير صالح' });  }});app.listen(3000);
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس