مقدمة في OAuth 2.0 و JWT في Swift
\n\nنادرًا ما تخلو تطبيقات الهواتف المحمولة وأجهزة الكمبيوتر المكتبية الحديثة من المصادقة. أصبح بروتوكولا رئيسيان — OAuth 2.0 و JWT (JSON Web Token) — المعيار الفعلي للنقل الآمن للبيانات بين العميل والخادم. في هذه المقالة، سنشرح كيفية تنفيذ OAuth 2.0 وتفويض JWT في Swift لمنصتي iOS و macOS. ستتعلم كيفية إعداد تدفق الحصول على الرموز المميزة، وتخزينها في Keychain، وتحديثها تلقائيًا عند انتهاء صلاحيتها.
\n\nالمقالة موجهة للمطورين المبتدئين الذين هم على دراية بأساسيات Swift و SwiftUI. سنستخدم URLSession لطلبات الشبكة و Keychain لتخزين البيانات بشكل آمن. الكود مكتوب بلغة Swift 5.9 ومتوافق مع iOS 15+ / macOS 12+.
\n\nما هو OAuth 2.0 و JWT؟
\n\nقبل كتابة الكود، دعنا نفهم النظرية.
\n\nOAuth 2.0
\nOAuth 2.0 هو بروتوكول تفويض يسمح للتطبيق بالحصول على وصول محدود إلى موارد المستخدم دون نقل اسم المستخدم وكلمة المرور الخاصة به. بدلاً من ذلك، يتم استخدام رموز الوصول. المشاركون الرئيسيون:
\n- \n
- مالك المورد (Resource Owner) — المستخدم. \n
- العميل (Client) — تطبيقك. \n
- خادم التفويض (Authorization Server) — الخادم الذي يصدر الرموز المميزة. \n
- خادم المورد (Resource Server) — API الذي يوفر البيانات. \n
التدفق الأكثر شيوعًا لتطبيقات الهاتف المحمول هو تدفق رمز التفويض مع PKCE (Proof Key for Code Exchange). إنه آمن حتى بدون client secret الذي يصعب تخزينه في التطبيق.
\n\nJWT (JSON Web Token)
\nJWT هو تنسيق رمز مميز مضغوط ومكتفي بذاته يحتوي على بيانات JSON. يتكون من ثلاثة أجزاء مفصولة بنقاط:
\n- \n
- الرأس (Header) — نوع الرمز المميز وخوارزمية التوقيع. \n
- الحمولة (Payload) — البيانات المفيدة (المطالبات)، مثل معرف المستخدم والدور وتاريخ انتهاء الصلاحية. \n
- التوقيع (Signature) — توقيع للتحقق من السلامة. \n
غالبًا ما يُستخدم JWT كـ رمز وصول (access token) في OAuth 2.0. يسمح للخادم بالتحقق من صحة الطلب دون الرجوع إلى قاعدة البيانات.
\n\nالتنفيذ العملي في Swift
\n\nسنقوم بإنشاء كلاس بسيط AuthService سيدير عملية المصادقة. على سبيل المثال، سنستخدم الخادم https://api.example.com.
الخطوة 1: الحصول على الرموز المميزة عبر OAuth 2.0
\n\nأولاً، سنقوم بتنفيذ طريقة للحصول على الرموز المميزة باستخدام رمز التفويض. لنفترض أننا حصلنا بالفعل على authorizationCode من خلال عرض ويب (مثل ASWebAuthenticationSession).
import Foundation\n\nfinal class AuthService {\n static let shared = AuthService()\n private let baseURL = "https://api.example.com"\n private let clientID = "your_client_id"\n private let redirectURI = "yourapp://callback"\n \n // MARK: - الحصول على الرموز المميزة\n func exchangeCodeForTokens(code: String, codeVerifier: String) async throws -> TokenResponse {\n let url = URL(string: "\\(baseURL)/oauth/token")!\n var request = URLRequest(url: url)\n request.httpMethod = "POST"\n request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")\n \n let body = [\n "grant_type": "authorization_code",\n "code": code,\n "redirect_uri": redirectURI,\n "client_id": clientID,\n "code_verifier": codeVerifier\n ]\n .map { "\\($0.key)=\\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")" }\n .joined(separator: "&")\n .data(using: .utf8)\n \n request.httpBody = body\n \n let (data, response) = try await URLSession.shared.data(for: request)\n \n guard let httpResponse = response as? HTTPURLResponse,