OpenID Connect (OIDC)

Authorization Code Flow & Token Exchange

OIDC هو طبقة هوية مبنية فوق OAuth 2.0 — يوفر معلومات هوية المستخدم (claims) عبر token آمن. الخيار المفضل للتطبيقات الحديثة.

نظرة عامة

OpenID Connect هو بروتوكول هوية مبني على OAuth 2.0 (RFC 6749). بينما OAuth 2.0 يجيب عن سؤال "هل المستخدم authorize التطبيق X؟"، يجيب OIDC عن سؤال "من المستخدم فعلياً؟".

الميزةOAuth 2.0OpenID Connect
الهدفAuthorization (صلاحية الوصول)Authentication + Authorization
TokenAccess Token فقطAccess Token + ID Token
UserInfoنعم (endpoint)
Discoveryنعم (.well-known/openid-configuration)
Scopeغير محدد للهويةopenid إلزامي

تدفق Authorization Code مع PKCE

التدفق الأكثر أماناً للتطبيقات الحديثة (SPAs, mobile apps). يستخدم PKCE للحماية من intercept attacks.

  1. التطبيق يُنشئ code_verifier و code_challenge
    const codeVerifier = generateRandomString(64);
    const codeChallenge = base64url(sha256(codeVerifier));
  2. إعادة التوجيه إلى authorization endpoint
    GET https://your-methaq-server/realms/{realm}/protocol/openid-connect/auth
      ?client_id=my-app
      &response_type=code
      &scope=openid profile email
      &redirect_uri=https://myapp.com/callback
      &state=random_state_string
      &code_challenge=YOUR_CODE_CHALLENGE
      &code_challenge_method=S256
  3. المستخدم يُصادق عبر ميثاق

    صفحة تسجيل الدخول المُدارة بواسطة ميثاق. يدعم MFA, Remember Me, Password Reset.

  4. ميثاق يُعيد التوجيه بـ authorization_code
    https://myapp.com/callback
      ?code=AUTH_CODE_HERE
      &state=random_state_string
  5. التطبيق يتبادل الـ code بـ tokens
    POST https://your-methaq-server/realms/{realm}/protocol/openid-connect/token
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code
    &code=AUTH_CODE_HERE
    &redirect_uri=https://myapp.com/callback
    &client_id=my-app
    &code_verifier=YOUR_CODE_VERIFIER
  6. ميثاق يُرجع tokens
    {
      "access_token": "eyJhbG...",
      "id_token": "eyJhbG...",
      "token_type": "Bearer",
      "expires_in": 300,
      "refresh_token": "refresh..."
    }

OIDC Discovery Endpoint

ميثاق يوفر configuration metadata تلقائياً — لا حاجة لحفظ URLs يدوياً.

GET https://your-methaq-server/realms/{realm}/.well-known/openid-configuration

الـ response:

{
  "issuer": "https://your-methaq-server/realms/{realm}",
  "authorization_endpoint": "https://your-methaq-server/realms/{realm}/protocol/openid-connect/auth",
  "token_endpoint": "https://your-methaq-server/realms/{realm}/protocol/openid-connect/token",
  "userinfo_endpoint": "https://your-methaq-server/realms/{realm}/protocol/openid-connect/userinfo",
  "jwks_uri": "https://your-methaq-server/realms/{realm}/protocol/openid-connect/certs",
  "end_session_endpoint": "https://your-methaq-server/realms/{realm}/protocol/openid-connect/logout",
  "scopes_supported": ["openid", "profile", "email", "offline_access"],
  "response_types_supported": ["code", "none", "token", "id_token"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "claims_supported": ["sub", "iss", "aud", "exp", "iat", "name", "email", "preferred_username"]
}

الرموز (Tokens)

Access Token

JWT signed بـ RS256 — يُرسل مع كل request للتحقق من الصلاحية. لا يحتوي على هوية المستخدم بشكل مباشر.

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-id-1"
}
---
{
  "iss": "https://your-methaq-server/realms/methaq",
  "sub": "user-uuid-here",
  "aud": "my-app",
  "exp": 1713000000,
  "iat": 1712996400,
  "scope": "openid profile email",
  "client_host": "203.0.113.42",
  "email": "user@example.com",
  "preferred_username": "johndoe"
}

ID Token

JWT يحتوي على claims هوية المستخدم — يُستخدم للتحقق من هوية المستخدم (وليس لصلاحية الوصول).

{
  "iss": "https://your-methaq-server/realms/methaq",
  "sub": "user-uuid-here",
  "aud": "my-app",
  "exp": 1712996400,
  "iat": 1712996400,
  "nonce": "random-nonce",
  "name": "John Doe",
  "given_name": "John",
  "family_name": "Doe",
  "email": "john@example.com",
  "email_verified": true,
  "preferred_username": "johndoe"
}

Refresh Token

يُستخدم للحصول على access token جديد بدون إعادة تسجيل الدخول. مدة صلاحيته الافتراضية في ميثاق: 15 دقيقة (قابلة للضبط).

POST /protocol/openid-connect/token
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN_HERE
&client_id=my-app

Scopes المدعومة

Scopeالـ Claims المرجعة
openidsub فقط (إلزامي)
profilename, given_name, family_name, preferred_username, picture
emailemail, email_verified
phonephone_number, phone_number_verified
offline_accessيرجع refresh_token (يلزم enable لـ Realm)
addressaddress claim
microprofile-jwtقيمة خاصة بـ MicroProfile JWT

مثال عملي: تسجيل تطبيق جديد

1. في لوحة تحكم ميثاق (Admin Console):

  1. اذهب إلى Clients ← انقر Create client
  2. Client ID: my-web-app
  3. Client Protocol: openid-connect
  4. Access Type: confidential (للتطبيقات السرية) أو public (لـ SPAs)
  5. Valid Redirect URIs: https://myapp.com/callback
  6. Web Origins: https://myapp.com
  7. من Credentials — احفظ Client Secret (للـ confidential clients)

2. Client Authentication Methods

الطريقةالاستخدامكيفية الإرسال
client_secret_basicConfidential clients (backend APIs)Authorization header
client_secret_postConfidential clientsPOST body
private_key_jwtSPAs, mobile, microservicesJWT signed with private key
nonePublic clients (no secret)PKCE إلزامي

كود التكامل

Node.js (express-openid-connect)

const { auth } = require('express-openid-connect');

const config = {
  issuerBaseURL: 'https://your-methaq-server/realms/methaq',
  clientID: 'my-web-app',
  clientSecret: 'YOUR_CLIENT_SECRET',
  baseURL: 'https://myapp.com',
  secret: 'LONG_RANDOM_SECRET',
  idpLogout: true,
};

app.use(auth(config));

// Protected route
app.get('/profile', (req, res) => {
  res.send(req.openid.user);
});

// Call protected API with access token
app.get('/api/data', async (req, res) => {
  const token = req.openid.accessToken;
  const response = await fetch('https://api.example.com/userinfo', {
    headers: { Authorization: `Bearer ${token}` }
  });
  res.json(await response.json());
});

Python (authlib)

from authlib.integrations.flask_client import OAuth

oauth = OAuth(app)
oauth.register('methaq',
    client_id='my-web-app',
    client_secret='YOUR_CLIENT_SECRET',
    server_metadata_url='https://your-methaq-server/realms/methaq/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid profile email'}
)

@app.route('/login')
def login():
    return oauth.methaq.authorize_redirect(redirect_uri=url_for('callback', _external=True))

@app.route('/callback')
def callback():
    token = oauth.methaq.authorize_access_token()
    user = token['userinfo']
    session['user'] = user
    return jsonify(user)