Einleitung: Warum OAuth 2.0 und JWT der Standard für iOS-Apps sind
\nModerne mobile Anwendungen arbeiten selten isoliert. Die meisten interagieren mit entfernten APIs und erfordern eine sichere Authentifizierung und Autorisierung des Benutzers. Im Apple-Ökosystem (iOS, macOS, watchOS) ist die Kombination aus OAuth 2.0 (Delegierungsprotokoll für Zugriffe) und JWT (JSON Web Token – kompaktes Token-Format) zum De-facto-Standard geworden. Diese Kombination bietet Flexibilität, Sicherheit und Integrationskomfort.
\nIn diesem Artikel werden wir nicht in die trockene Theorie eintauchen. Stattdessen werden wir eine praktische Implementierung in Swift durchgehen. Sie erfahren, wie Sie ein Zugriffstoken über OAuth 2.0 erhalten, es sicher in der Keychain speichern, JWT auf dem Client decodieren und Tokens ohne Benutzereingriff aktualisieren. Alle Codebeispiele sind bereit für den Einsatz in einem echten Projekt.
\n\n1. OAuth 2.0-Architektur in Swift: Von der Anfrage zum Token
\nZunächst legen wir den Ablauf (Flow) fest. In mobilen Apps wird am häufigsten der Authorization Code Flow mit PKCE (Proof Key for Code Exchange) verwendet. Dies ist die sicherste Option für öffentliche Clients (mobile Apps können kein Geheimnis speichern).
\nWir erstellen eine Struktur für die OAuth-Konfiguration:
\nstruct OAuthConfig {\n let clientID: String\n let redirectURI: String\n let authorizationEndpoint: String\n let tokenEndpoint: String\n let scope: String\n}\nDer erste Schritt ist die Erstellung der URL für die Autorisierung. Für PKCE benötigen wir einen generierten codeVerifier und seinen SHA256-Hash – den codeChallenge:
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}\nJetzt erstellen wir die URL, die im Browser geöffnet wird (über ASWebAuthenticationSession):
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}\nNach erfolgreicher Autorisierung wird der Benutzer auf die redirectURI mit dem Parameter code weitergeleitet. Dieser Code wird gegen Tokens eingetauscht:
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