OAuth 2.0 и JWT авторизация на Swift: полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в OAuth 2.0 и JWT на Swift

Современные мобильные и десктопные приложения на Swift (iOS/macOS) всё чаще используют протокол OAuth 2.0 и формат JWT (JSON Web Token) для безопасной авторизации. OAuth 2.0 обеспечивает делегированный доступ к ресурсам, а JWT позволяет компактно и безопасно передавать данные пользователя. В этой статье мы разберём, как реализовать полную цепочку авторизации на Swift: от получения токена до его проверки и обновления.

Мы рассмотрим:

  • Основы OAuth 2.0: потоки (flows) и роли
  • Структуру JWT и его декодирование на Swift
  • Практическую реализацию авторизации с использованием URLSession
  • Хранение токенов в связке ключей (Keychain) и их обновление

Основы OAuth 2.0 для Swift-разработчика

OAuth 2.0 — это протокол авторизации, который позволяет приложению получать ограниченный доступ к ресурсам пользователя без передачи его учётных данных. В контексте Swift приложений чаще всего используется Authorization Code Flow с PKCE (Proof Key for Code Exchange) для мобильных приложений.

Ключевые компоненты OAuth 2.0

  • Resource Owner — пользователь, владелец данных
  • Client — ваше Swift-приложение
  • Authorization Server — сервер, выдающий токены (например, Google, Facebook, ваш бэкенд)
  • Resource Server — API, предоставляющее защищённые данные

Типичный поток на Swift выглядит так:

  1. Приложение открывает веб-вью (SFSafariViewController или ASWebAuthenticationSession) для аутентификации пользователя.
  2. После успешного входа сервер перенаправляет на callback URL с временным кодом.
  3. Приложение обменивает код на access token и refresh token.
  4. Access token используется для запросов к API, refresh token — для получения нового access token при истечении срока.

Структура JWT и его декодирование на Swift

JWT состоит из трёх частей, разделённых точками: header.payload.signature. Пример JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikl2YW4gUGV0cm92IiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Декодирование payload на Swift (без проверки подписи):

import Foundation

struct JWT {
    static func decodePayload(_ token: String) -> [String: Any]? {
        let segments = token.components(separatedBy: ".")
        guard segments.count == 3 else { return nil }
        
        var base64 = segments[1]
            .replacingOccurrences(of: "-", with: "+")
            .replacingOccurrences(of: "_", with: "/")
        
        let length = Double(base64.lengthOfBytes(using: .utf8))
        let requiredLength = 4 * ceil(length / 4.0)
        let padding = Int(requiredLength - length)
        if padding > 0 {
            base64 += String(repeating: "=", count: padding)
        }
        
        guard let data = Data(base64Encoded: base64) else { return nil }
        return try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
    }
}

// Использование
if let payload = JWT.decodePayload("your.jwt.token") {
    print(payload["sub"] ?? "")
    print(payload["exp"] ?? "")
}

Для полной проверки подписи JWT на стороне клиента используйте библиотеку SwiftJWT или JWTDecode, доступные через Swift Package Manager.

Реализация OAuth 2.0 и JWT на Swift

Шаг 1: Настройка URLSession и модели токенов

struct AuthToken: Codable {
    let accessToken: String
    let refreshToken: String?
    let expiresIn: Int
    let tokenType: String
}

class AuthService {
    private let session = URLSession.shared
    private let baseURL = "https://api.example.com"
    
    func exchangeCodeForToken(code: String, completion: @escaping (Result<AuthToken, Error>) -> Void) {
        let url = URL(string: "\(baseURL)/oauth/token")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        
        let body = "grant_type=authorization_code&code=\(code)&redirect_uri=yourapp://callback&client_id=YOUR_CLIENT_ID&code_verifier=VERIFIER"
        request.httpBody = body.data(using: .utf8)
        
        session.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            guard let data = data else {
                completion(.failure(NSError(domain: "No data", code: -1)))
                return
            }
            do {
                let token = try JSONDecoder().decode(AuthToken.self, from: data)
                completion(.success(token))
            } catch {
                completion(.failure(error))
            }
        }.resume()
    }
}

Шаг 2: Хранение токенов в Keychain

Для безопасного хранения токенов используйте Keychain Services. Пример простой обёртки:

import Security

class KeychainManager {
    static func save(key: String, data: Data) -> Bool {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecValueData: data
        ] as CFDictionary
        SecItemDelete(query)
        return SecItemAdd(query, nil) == errSecSuccess
    }
    
    static func load(key: String) -> Data? {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecReturnData: true,
            kSecMatchLimit: kSecMatchLimitOne
        ] as CFDictionary
        var result: AnyObject?
        SecItemCopyMatching(query, &result)
        return result as? Data
    }
}

Шаг 3: Автоматическое обновление токена

Когда access token истекает, используйте refresh token для получения нового:

func refreshAccessToken(refreshToken: String, completion: @escaping (Result<AuthToken, Error>) -> Void) {
    let url = URL(string: "\(base                

Блоги

Рекомендаций по книгам