Authorization Code Flow & Token Exchange
OIDC هو طبقة هوية مبنية فوق OAuth 2.0 — يوفر معلومات هوية المستخدم (claims) عبر token آمن. الخيار المفضل للتطبيقات الحديثة.
OpenID Connect هو بروتوكول هوية مبني على OAuth 2.0 (RFC 6749). بينما OAuth 2.0 يجيب عن سؤال "هل المستخدم authorize التطبيق X؟"، يجيب OIDC عن سؤال "من المستخدم فعلياً؟".
التدفق الأكثر أماناً للتطبيقات الحديثة (SPAs, mobile apps). يستخدم PKCE للحماية من intercept attacks.
const codeVerifier = generateRandomString(64);
const codeChallenge = base64url(sha256(codeVerifier));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صفحة تسجيل الدخول المُدارة بواسطة ميثاق. يدعم MFA, Remember Me, Password Reset.
https://myapp.com/callback
?code=AUTH_CODE_HERE
&state=random_state_stringPOST 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{
"access_token": "eyJhbG...",
"id_token": "eyJhbG...",
"token_type": "Bearer",
"expires_in": 300,
"refresh_token": "refresh..."
}ميثاق يوفر 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"]
}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"
}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"
}يُستخدم للحصول على access token جديد بدون إعادة تسجيل الدخول. مدة صلاحيته الافتراضية في ميثاق: 15 دقيقة (قابلة للضبط).
POST /protocol/openid-connect/token
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN_HERE
&client_id=my-app| Scope | الـ Claims المرجعة |
|---|---|
openid | sub فقط (إلزامي) |
profile | name, given_name, family_name, preferred_username, picture |
email | email, email_verified |
phone | phone_number, phone_number_verified |
offline_access | يرجع refresh_token (يلزم enable لـ Realm) |
address | address claim |
microprofile-jwt | قيمة خاصة بـ MicroProfile JWT |
my-web-appopenid-connectconfidential (للتطبيقات السرية) أو public (لـ SPAs)https://myapp.com/callbackhttps://myapp.comClient Secret (للـ confidential clients)| الطريقة | الاستخدام | كيفية الإرسال |
|---|---|---|
client_secret_basic | Confidential clients (backend APIs) | Authorization header |
client_secret_post | Confidential clients | POST body |
private_key_jwt | SPAs, mobile, microservices | JWT signed with private key |
none | Public clients (no secret) | PKCE إلزامي |
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());
});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)