عميل OAuth لـ TypeScript: دليل كامل لتنفيذ التفويض
في تطوير الويب الحديث، تعد المصادقة الآمنة للمستخدمين إحدى المهام الرئيسية. OAuth 2.0 هو بروتوكول تفويض مفتوح يسمح للتطبيقات بالحصول على وصول محدود إلى موارد المستخدم دون نقل اسم المستخدم وكلمة المرور. في هذه المقالة، سنشرح كيفية إنشاء عميل OAuth بلغة TypeScript يمكن استخدامه في أي تطبيق ويب. المكتبة مناسبة للتكامل مع Google وGitHub وFacebook ومزودين آخرين.
ما هو عميل OAuth بلغة TypeScript؟
عميل OAuth هو مجموعة من الدوال والفئات التي تنفذ تدفق التفويض (Authorization Code Flow) على جانب العميل أو الخادم. باستخدام TypeScript، يوفر هذا العميل كتابة صارمة للأنواع، مما يقلل من عدد الأخطاء ويبسط صيانة الكود. المهام الرئيسية للعميل:
- توليد URL لإعادة توجيه المستخدم إلى صفحة المزود.
- تبادل الكود المؤقت مقابل access token و refresh token.
- تخزين وتحديث الرموز.
- التحقق من صحة معاملات الطلب (state, nonce).
هندسة المكتبة
تتكون المكتبة من عدة وحدات:
OAuthClient— الفئة الرئيسية التي تدير جميع المراحل.TokenStore— واجهة لتخزين الرموز (يمكن تنفيذها في الذاكرة، أو localStorage، أو قاعدة البيانات).ProviderConfig— تكوين خاص بمزود معين (clientId, redirectUri, endpoints).AuthError— فئة لمعالجة أخطاء التفويض.
مثال على التنفيذ باستخدام TypeScript
فيما يلي مثال أساسي لعميل OAuth بلغة TypeScript. أولاً، نحدد الأنواع والواجهات:
// types.tsexport interface TokenResponse { access_token: string; token_type: string; expires_in: number; refresh_token?: string; scope?: string;}
export interface ProviderConfig { authorizationEndpoint: string; tokenEndpoint: string; clientId: string; redirectUri: string; scope: string;}
export interface TokenStore { getToken(): Promise<string | null>; setToken(token: TokenResponse): Promise<void>; clearToken(): Promise<void>;}الآن، ننشئ الفئة الرئيسية OAuthClient:
// OAuthClient.tsimport { TokenResponse, ProviderConfig, TokenStore } from './types';import { AuthError } from './AuthError';
export class OAuthClient { private config: ProviderConfig; private store: TokenStore;
constructor(config: ProviderConfig, store: TokenStore) { this.config = config; this.store = store; }
// توليد URL للتفويض getAuthorizationUrl(state: string): string { const params = new URLSearchParams({ client_id: this.config.clientId, redirect_uri: this.config.redirectUri, response_type: 'code', scope: this.config.scope, state: state, }); return `${this.config.authorizationEndpoint}?${params.toString()}`; }
// تبادل الكود مقابل الرمز async exchangeCodeForToken(code: string, state: string, expectedState: string): Promise<TokenResponse> { if (state !== expectedState) { throw new AuthError('State mismatch. Possible CSRF attack.'); }
const body = new URLSearchParams({ grant_type: 'authorization_code', code: code, redirect_uri: this.config.redirectUri, client_id: this.config.clientId, });
const response = await fetch(this.config.tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body, });
if (!response.ok)