OAuth 2.0 y autorización JWT en Swift: guía práctica

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: por qué OAuth 2.0 y JWT son el estándar para aplicaciones iOS

\n

Las aplicaciones móviles modernas rara vez funcionan de forma aislada. La mayoría interactúa con APIs remotas, requiriendo autenticación y autorización segura del usuario. En el ecosistema de Apple (iOS, macOS, watchOS), el estándar de facto es la combinación de OAuth 2.0 (protocolo de delegación de acceso) y JWT (JSON Web Token — formato compacto de token). Esta combinación proporciona flexibilidad, seguridad y facilidad de integración.

\n

En este artículo no profundizaremos en teoría abstracta. En su lugar, analizaremos una implementación práctica en Swift. Aprenderá cómo obtener un token de acceso mediante OAuth 2.0, cómo almacenarlo de forma segura en Keychain, cómo decodificar JWT en el cliente y cómo renovar tokens sin intervención del usuario. Todos los ejemplos de código están listos para usar en un proyecto real.

\n\n

1. Arquitectura de OAuth 2.0 en Swift: desde la solicitud hasta el token

\n

Para empezar, definamos el flujo. En aplicaciones móviles, el más utilizado es el Authorization Code Flow with PKCE (Proof Key for Code Exchange). Esta es la opción más segura para clientes públicos (las aplicaciones móviles no pueden almacenar un secreto).

\n

Crearemos una estructura para la configuración de 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

El primer paso es construir la URL de autorización. Para PKCE necesitamos generar un codeVerifier y su hash 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

Ahora construimos la URL para abrir en el navegador (a través de 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

Después de una autorización exitosa, el usuario será redirigido a redirectURI con el parámetro code. Este código se intercambia por tokens:

\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                

Blogs

Book Recommendations