انتقل إلى المحتوى الرئيسي

Token Lifecycle

Ithbat IAM يصدر 3 أنواع من الـ Tokens عند نجاح المصادقة: Access Token و Refresh Token و ID Token. فهم آلية عمل هذه الـ Tokens وانتهاء صلاحيتها وتدويرها أمر أساسي لبناء integration آمن وموثوق.


أنواع الـ Tokens

Access Token

الخاصيةالقيمة
الصيغةJWT (RS256)
الصلاحية الافتراضيةساعة واحدة (OIDC_ACCESS_TOKEN_LIFESPAN)
الاستخداميُرسل كـ Authorization: Bearer <token> مع كل API Request

الـ Access Token هو JWT موقّع. يحتوي على identity claims المستخدم ويتحقق منه API server باستخدام public key المستفيد. يجب على الـ backend لديك التحقق من JWT signature في كل Request — لا تستدعي Ithbat IAM في كل Request للتحقق من الـ Tokens.

Standard claims:

{
"sub": "usr_01J8X...",
"iss": "https://api.ithbat.io",
"aud": "https://api.ithbat.io",
"exp": 1740400800,
"iat": 1740397200,
"jti": "tok_01J8X..."
}

Custom claims (Ithbat extensions):

{
"tenant_id": "ten_01J8X...",
"email": "[email protected]",
"display_name": "Sara Al-Rashidi",
"roles": ["admin"],
"permissions": ["user:read", "user:write"],
"region": "ksa"
}

Refresh Token

الخاصيةالقيمة
الصيغةOpaque (مُجزّأ، ليس JWT)
الصلاحية الافتراضية30 يومًا (OIDC_REFRESH_TOKEN_LIFESPAN = 720h)
التدويرأحادي الاستخدام — Refresh Token جديد يُصدر مع كل عملية refresh

الـ Refresh Token يُخزَّن على السيرفر كـ SHA-256 hash. لا يمكن فك ترميزه لكشف معلومات المستخدم. إذا Refresh Token مستهلَك أُعيد استخدامه، السيرفر يلغي عائلة الـ Tokens كاملة (rotation with reuse detection).

ID Token

الخاصيةالقيمة
الصيغةJWT (RS256, OIDC-compliant)
الاستخدامClient-side identity assertions، ليس للـ API authorization

الـ ID Token يتبع مواصفات OIDC ويحتوي معلومات profile المستخدم. استخدمه لعرض معلومات المستخدم في واجهتك. لا تستخدمه كـ authorization credential لـ API calls.


Token Refresh Flow

عند انتهاء صلاحية الـ Access Token، استخدم الـ Refresh Token للحصول على زوج جديد دون أن تطلب من المستخدم تسجيل الدخول مجدداً.

POST /api/v1/auth/refresh

الـ Request

{
"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..."
}

الـ Response — 200 OK

{
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "bmV3UmVmcmVzaFRva2Vu...",
"idToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 3600
}
تحذير

الـ Refresh Token القديم يُلغى فورًا بعد refresh ناجح. خزّن refreshToken الجديد من الـ Response. إعادة استخدام القديم تفعّل reuse detection وتلغي كل Sessions المستخدم.

sequenceDiagram
participant Client
participant API as Ithbat IAM

Client->>API: POST /api/v1/auth/refresh { refreshToken }
API->>API: Validate refresh token, check expiry
API->>API: Generate new access token + new refresh token
API->>API: Revoke old refresh token
API-->>Client: { accessToken, refreshToken, idToken, expiresIn }
Client->>Client: Store new tokens, discard old

Token Validation

تحقق من Access Tokens في الـ backend لديك باستخدام JWKS Endpoint الخاص بالمستفيد.

JWKS Endpoint:

GET https://api.ithbat.io/.well-known/jwks.json

OIDC Discovery:

GET https://api.ithbat.io/.well-known/openid-configuration

أغلب JWT libraries تدعم JWKS key rotation تلقائيًا. هيّئها لجلب المفاتيح من JWKS Endpoint بدلاً من تثبيت public key في الكود.

نصيحة

خزّن JWKS Response مؤقتًا وأعد جلبه عند مواجهة kid في Token غير موجود في الـ cache لديك. هذا يتجنب network call في كل Request مع التعامل مع key rotation.


إدارة الـ Sessions

كل عملية تسجيل دخول تنشئ Session على السيرفر تُتتبع في Redis. الـ Sessions تخزّن metadata (IP address, device type, location) للرؤية الأمنية وتتيح الإلغاء المستهدف.

عرض الـ Sessions

GET /api/v1/auth/sessions

Header مطلوب: Authorization: Bearer <accessToken>

مرّر X-Session-ID لتحديد الـ Session الحالية (تُعلَّم بـ isCurrent: true في الـ Response).

الـ Response — 200 OK

[
{
"id": "sess_01J8X...",
"deviceType": "desktop",
"deviceName": "Chrome on macOS",
"ipAddress": "41.64.12.100",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...",
"location": {
"country": "SA",
"city": "Riyadh"
},
"createdAt": "2026-02-20T08:30:00Z",
"lastActivity": "2026-02-24T09:45:00Z",
"isCurrent": true
},
{
"id": "sess_01J9Y...",
"deviceType": "mobile",
"deviceName": "Safari on iPhone",
"ipAddress": "197.34.11.55",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)...",
"location": {
"country": "EG",
"city": "Cairo"
},
"createdAt": "2026-02-18T14:00:00Z",
"lastActivity": "2026-02-23T20:10:00Z",
"isCurrent": false
}
]

إلغاء Session محددة

DELETE /api/v1/auth/sessions/&#123;id&#125;

Header مطلوب: Authorization: Bearer <accessToken>

الـ Response — 200 OK

{
"message": "Session revoked successfully"
}

إلغاء كل الـ Sessions الثانية

يلغي كل الـ Sessions إلا الحالية. مفيد لـ "تسجيل الخروج من كل الأجهزة الثانية".

DELETE /api/v1/auth/sessions

Header مطلوب: Authorization: Bearer <accessToken>

الـ Response — 200 OK

{
"message": "All other sessions revoked successfully"
}

التحديث التلقائي (SDK)

الـ Ithbat SDK يتولى Token refresh تلقائيًا. إذا Request فشل بـ 401 Unauthorized بسبب Access Token منتهي، الـ SDK يعترض الـ Response، يحدّث الـ Token، ويعيد محاولة الـ Request الأصلي بشفافية.

import { IthbatSDK } from '@ithbatiam/sdk';

const ithbat = new IthbatSDK({
tenantId: 'ten_01J8X...',
baseUrl: 'https://api.ithbat.io',
// SDK manages token storage and refresh automatically
storage: 'localStorage', // or 'memory' for server-side apps
});

// This request will transparently refresh the token if needed
const user = await ithbat.users.getMe();

التحديث اليدوي (بدون SDK)

async function refreshTokens(refreshToken) {
const resp = await fetch('https://api.ithbat.io/api/v1/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});

if (!resp.ok) {
// Refresh token is expired or invalid — redirect to login
throw new Error('Session expired');
}

const tokens = await resp.json();
// Store tokens.accessToken and tokens.refreshToken
return tokens;
}

أفضل ممارسات تخزين الـ Tokens

خيار التخزينخطر XSSخطر CSRFالتوصية
localStorageعالي (JavaScript يوصله)لا يوجدتجنّبه للتطبيقات الحساسة
sessionStorageعالي (JavaScript يوصله)لا يوجدمقبول لـ session-scoped SPAs
HttpOnly cookieلا يوجدمتوسط (خفّفه بـ SameSite)الخيار المفضّل لتطبيقات الويب
In-memory (variable)لا يوجد (يُمسح عند refresh)لا يوجدالأفضل لـ SPAs ذات Sessions قصيرة
نصيحة

لتطبيقات الويب، خزّن الـ Access Token في الذاكرة والـ Refresh Token في HttpOnly cookie مع Secure و SameSite=Strict. الـ Access Token قصير الصلاحية (ساعة واحدة) ويمكنك الحصول عليه من جديد من refresh cookie عند تحديث الصفحة.

خطر

لا تخزّن Tokens أبدًا في localStorage لتطبيقات تتعامل مع بيانات حساسة. ثغرات XSS يمكنها تسريب جميع الـ Tokens من localStorage بصمت.


أفضل ممارسات الأمان

Refresh Token Rotation

Ithbat IAM يستخدم strict refresh token rotation. كل refresh يلغي الـ Token السابق ويصدر واحد جديد. إذا النظام اكتشف إن الـ Token القديم اُستخدم مرة ثانية بعد الـ rotation، كل Sessions المستخدم تُلغى فورًا.

إلغاء الـ Tokens عند تغيير كلمة المرور

إذا المستخدم غيّر كلمة مروره مع revoke_other_sessions: true، كل Refresh Tokens للمستخدم تُلغى. الـ Access Tokens النشطة تظل صالحة حتى تنتهي صلاحيتها الطبيعية (حتى ساعة). إذا تحتاج إلغاء فوري، قلّل قيمة OIDC_ACCESS_TOKEN_LIFESPAN.

إلغاء الـ Tokens عند تسجيل الخروج

POST /api/v1/auth/logout تلغي كل Refresh Tokens وتحذف كل الـ Sessions على السيرفر للمستخدم. الـ Access Tokens النشطة ما تنلغي استباقيًا — تظل صالحة حتى تنتهي. صمّم نظامك بحيث Access Token expiry يكون قصير (15-60 دقيقة) إذا هذا مهم لك.


أمثلة الكود

cURL — تحديث الـ Tokens

curl -X POST https://api.ithbat.io/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..."}'

cURL — عرض الـ Sessions

curl https://api.ithbat.io/api/v1/auth/sessions \
-H "Authorization: Bearer eyJhbGci..." \
-H "X-Session-ID: sess_01J8X..."

cURL — إلغاء كل الـ Sessions الثانية

curl -X DELETE https://api.ithbat.io/api/v1/auth/sessions \
-H "Authorization: Bearer eyJhbGci..." \
-H "X-Session-ID: sess_01J8X..."

JavaScript — فك ترميز Access Token Claims

function decodeJwtPayload(token) {
const [, payloadB64] = token.split('.');
const json = atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/'));
return JSON.parse(json);
}

const claims = decodeJwtPayload(accessToken);
console.log(claims.sub); // usr_01J8X...
console.log(claims.tenant_id); // ten_01J8X...
console.log(claims.roles); // ['admin']
console.log(claims.permissions); // ['user:read', 'user:write']
console.log(new Date(claims.exp * 1000)); // expiry date

معالجة الأخطاء

HTTP StatusError Codeالسبب
400VALIDATION_ERRORحقل refreshToken ناقص
401UNAUTHORIZEDRefresh Token ليس موجود (مستخدَم أو مُلغى)
401UNAUTHORIZEDRefresh Token منتهي الصلاحية (30 يوم انقضت)
403FORBIDDENحساب المستخدم موقوف أو معطّل

الخطوات التالية