OAuth 2.0 و JWT التفويض على Swift: دليل عملي

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا OAuth 2.0 و JWT هما المعيار لتطبيقات iOS

\n

تطبيقات الهواتف المحمولة الحديثة نادراً ما تعمل بشكل منعزل. معظمها يتفاعل مع واجهات برمجة التطبيقات (API) البعيدة، مما يتطلب مصادقة وتفويضاً آمناً للمستخدم. في نظام Apple البيئي (iOS، macOS، watchOS)، أصبح الجمع بين OAuth 2.0 (بروتوكول تفويض الوصول) و JWT (رمز JSON للويب - تنسيق مضغوط للرمز المميز) هو المعيار الفعلي. يوفر هذا المزيج المرونة والأمان وسهولة التكامل.

\n

في هذه المقالة، لن نتعمق في النظرية الجافة. بدلاً من ذلك، سنقوم بتحليل التنفيذ العملي على Swift. ستتعلم كيفية الحصول على رمز الوصول عبر OAuth 2.0، وكيفية تخزينه بأمان في Keychain، وكيفية فك تشفير JWT على العميل، وكيفية تحديث الرموز المميزة دون تدخل المستخدم. جميع أمثلة الكود جاهزة للاستخدام في مشروع حقيقي.

\n\n

1. بنية OAuth 2.0 على Swift: من الطلب إلى الرمز المميز

\n

أولاً، دعنا نحدد التدفق (flow). في تطبيقات الهواتف المحمولة، يُستخدم غالباً Authorization Code Flow with PKCE (إثبات المفتاح لتبادل الكود). هذا هو الخيار الأكثر أماناً للعملاء العموميين (تطبيقات الهواتف المحمولة لا يمكنها تخزين السر).

\n

لنقم بإنشاء هيكل لتكوين OAuth:

\n
struct OAuthConfig {\n    let clientID: String\n    let redirectURI: String\n    let authorizationEndpoint: String\n    let tokenEndpoint: String\n    let scope: String\n}
\n

الخطوة الأولى هي تشكيل عنوان URL للتفويض. بالنسبة لـ PKCE، سنحتاج إلى توليد codeVerifier وتجزئته SHA256 - codeChallenge:

\n
import Foundation\nimport CommonCrypto\n\nclass PKCEGenerator {\n    static func generateCodeVerifier() -> String {\n        var buffer = [UInt8](repeating: 0, count: 32)\n        _ = SecRandomCopyBytes(kSecRandomDefault, buffer.count, &buffer)\n        return Data(buffer).base64URLEncodedString()\n    }\n    \n    static func generateCodeChallenge(from verifier: String) -> String {\n        guard let data = verifier.data(using: .utf8) else { return "" }\n        var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))\n        data.withUnsafeBytes { \n            _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)\n        }\n        return Data(hash).base64URLEncodedString()\n    }\n}\n\nextension Data {\n    func base64URLEncodedString() -> String {\n        return self.base64EncodedString()\n            .replacingOccurrences(of: "+", with: "-")\n            .replacingOccurrences(of: "/", with: "_")\n            .replacingOccurrences(of: "=", with: "")\n    }\n}
\n

الآن نقوم بتشكيل عنوان URL لفتحه في المتصفح (عبر ASWebAuthenticationSession):

\n
func buildAuthorizationURL(config: OAuthConfig, codeChallenge: String) -> URL? {\n    var components = URLComponents(string: config.authorizationEndpoint)\n    components?.queryItems = [\n        URLQueryItem(name: "response_type", value: "code"),\n        URLQueryItem(name: "client_id", value: config.clientID),\n        URLQueryItem(name: "redirect_uri", value: config.redirectURI),\n        URLQueryItem(name: "scope", value: config.scope),\n        URLQueryItem(name: "code_challenge", value: codeChallenge),\n        URLQueryItem(name: "code_challenge_method", value: "S256")\n    ]\n    return components?.url\n}
\n

بعد التفويض الناجح، سيتم إعادة توجيه المستخدم إلى redirectURI مع معامل code. يتم استبدال هذا الكود بالرموز المميزة:

\n
func exchangeCodeForToken(config: OAuthConfig, code: String, codeVerifier: String) async throws -> TokenResponse {\n    let url = URL(string: config.tokenEndpoint)!\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 = "grant_type=authorization_code" +\n               "&co                

المدونات

توصيات الكتب