OAuth 2.0 y JWT son los patrones dominantes para asegurar APIs móviles, pero probarlos correctamente requiere conocimiento especializado del ciclo de vida de tokens y casos extremos de seguridad. Según el OWASP API Security Top 10 2023, la autenticación y autorización rotas siguen siendo las principales vulnerabilidades en las APIs, con los fallos de implementación JWT directamente responsables de brechas de alto perfil. Según un estudio de Auth0 (ahora Okta), el 63% de las apps móviles tienen al menos una vulnerabilidad de autenticación en su código de manejo de tokens. Para los ingenieros de QA, probar flujos OAuth/JWT significa validar el ciclo de vida completo del token.
TL;DR: El testing OAuth/JWT para apps móviles debe cubrir: flujo completo de authorization code (PKCE para móvil), validación de access token (firma, expiración, audience), rotación de refresh token, aplicación de scope, revocación de token y ataques de seguridad (algorithm confusion, JWT none algorithm).
Flujos OAuth 2.0 para Móviles
Authorization Code Flow con PKCE
PKCE (Proof Key for Code Exchange) previene ataques de intercepción de códigos de autorización, haciéndolo esencial para apps móviles.
// Usando biblioteca AppAuth
class OAuthManager(private val context: Context) {
fun startAuthFlow(activity: Activity) {
val authRequest = AuthorizationRequest.Builder(
serviceConfig,
CLIENT_ID,
ResponseTypeValues.CODE,
Uri.parse("myapp://oauth-callback")
)
.setCodeVerifier(CodeVerifierUtil.generateRandomCodeVerifier())
.setScope("openid profile email")
.build()
val authService = AuthorizationService(context)
val intent = authService.getAuthorizationRequestIntent(authRequest)
activity.startActivityForResult(intent, AUTH_REQUEST_CODE)
}
}
Testing Flujo OAuth
@Test
fun testAuthorizationCodeExchange() = runTest {
val mockAuthServer = MockWebServer()
mockAuthServer.enqueue(MockResponse()
.setResponseCode(200)
.setBody("""
{
"access_token": "mock_access_token",
"refresh_token": "mock_refresh_token",
"expires_in": 3600
}
""".trimIndent())
)
val result = oauthManager.exchangeAuthCode("mock_auth_code")
assertNotNull(result.accessToken)
assertEquals("Bearer", result.tokenType)
}
“OAuth and JWT testing is where you earn your security credibility as a QA engineer. Getting token validation right — especially edge cases like expired tokens, revoked tokens, and algorithm confusion attacks — is non-trivial and critically important.” — Yuri Kan, Senior QA Lead
Testing de Tokens JWT
class JWTValidator {
fun validate(token: String): Boolean {
val parts = token.split(".")
if (parts.size != 3) return false
val payload = decodePayload(parts[1])
if (payload.exp * 1000 < System.currentTimeMillis()) {
throw TokenExpiredException()
}
return verifySignature(parts[0], parts[1], parts[2])
}
}
@Test
fun testJWTValidation() {
val validToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
assertTrue(validator.validate(validToken))
}
Flujo de Refresh de Tokens
class TokenManager(private val authService: AuthService) {
suspend fun getValidAccessToken(): String {
if (isTokenExpired()) {
refreshAccessToken()
}
return accessToken ?: throw NoTokenException()
}
private suspend fun refreshAccessToken() {
val response = authService.refreshToken(refreshToken!!)
accessToken = response.accessToken
expiresAt = System.currentTimeMillis() + (response.expiresIn * 1000)
}
}
@Test
fun testAutomaticTokenRefresh() = runTest {
tokenManager.setToken("expired_token", expiresAt = System.currentTimeMillis() - 1000)
val validToken = tokenManager.getValidAccessToken()
assertEquals("new_access_token", validToken)
}
Testing Autenticación Biométrica
class BiometricAuthManager(private val context: FragmentActivity) {
fun authenticate(onSuccess: () -> Unit, onFailure: (String) -> Unit) {
val biometricPrompt = BiometricPrompt(
context,
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
onSuccess()
}
override fun onAuthenticationFailed() {
onFailure("Autenticación fallida")
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Login biométrico")
.setSubtitle("Inicia sesión usando tu credencial biométrica")
.setNegativeButtonText("Usar contraseña")
.build()
biometricPrompt.authenticate(promptInfo)
}
}
Almacenamiento Seguro de Tokens
El almacenamiento seguro de tokens es tan crítico como la seguridad de red. Mientras que certificate pinning protege datos en tránsito, el almacenamiento apropiado previene acceso no autorizado en reposo.
class SecureTokenStorage(private val context: Context) {
private val keyStore = KeyStore.getInstance("AndroidKeyStore")
fun saveToken(token: String) {
val cipher = cipher
val encryptedData = cipher.doFinal(token.toByteArray())
context.getSharedPreferences("auth", Context.MODE_PRIVATE)
.edit()
.putString("token", Base64.encodeToString(encryptedData, Base64.DEFAULT))
.apply()
}
fun getToken(): String? {
val prefs = context.getSharedPreferences("auth", Context.MODE_PRIVATE)
val encryptedData = prefs.getString("token", null) ?: return null
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val decryptedData = cipher.doFinal(Base64.decode(encryptedData, Base64.DEFAULT))
return String(decryptedData)
}
}
Mejores Prácticas
- Siempre usa PKCE para OAuth móvil - Previene intercepción de código
- Almacena tokens de forma segura - Usa Android Keystore / iOS Keychain
- Implementa refresh automático - Experiencia de usuario fluida
- Prueba escenarios de expiración - Maneja casos extremos
- Usa autenticación biométrica - Seguridad mejorada + UX
Para una visión completa de prácticas modernas de testing móvil, incluyendo integración de testing OAuth, consulta nuestra guía Mobile Testing 2025. También explora Maestría en Testing de API para estrategias completas de testing de API.
Conclusión
El testing OAuth 2.0 y JWT para móviles requiere:
- Implementación y testing de flujo PKCE
- Gestión de ciclo de vida de tokens
- Validación de almacenamiento seguro
- Integración de autenticación biométrica
- Manejo de errores y casos extremos
El testing apropiado de autenticación asegura seguridad mientras mantiene experiencia de usuario fluida a través de ciclos de vida de la app.
Ver También
- Testing de Seguridad Móvil - Estrategias completas de seguridad para apps móviles
- Testing de Seguridad de API - Pruebas de vulnerabilidades en endpoints de autenticación
- Testing de Pagos Móviles - Seguridad en transacciones con OAuth
- Maestría en Testing de API - Fundamentos de testing de API REST
- Mobile Testing 2025: iOS, Android y Más Allá - Tendencias modernas en testing móvil
Recursos Oficiales
FAQ
¿Qué es PKCE y por qué es requerido para OAuth móvil?
PKCE (Proof Key for Code Exchange) previene ataques de interceptación de authorization code en apps móviles donde los client secrets no pueden almacenarse de forma segura. En lugar de un client secret fijo, PKCE usa un code_verifier generado dinámicamente y su hash SHA-256 (code_challenge). Toda implementación OAuth móvil debería usar PKCE.
¿Cómo pruebo la validación JWT correctamente?
Prueba estos casos: token válido, token expirado (debería devolver 401), firma inválida (401), algoritmo incorrecto (ataque de confusión de algoritmo), token con algoritmo none (siempre debe rechazarse), claim de audience incorrecto y claims requeridos faltantes.
¿Cómo pruebo la aplicación de scope OAuth?
Crea tokens con diferentes subconjuntos de scope. Intenta acceder a recursos que requieren scopes no incluidos en el token. Verifica: los recursos devuelven 403 (no 401) para tokens válidos con scope insuficiente, y que la escalación de scope está correctamente bloqueada.
¿Cómo pruebo la rotación de refresh token?
Verifica: el refresh token puede usarse una vez para obtener un nuevo par de access/refresh token. Verifica que el refresh token anterior se invalida inmediatamente después del uso. Prueba la detección de reutilización de refresh token y la expiración de refresh tokens de larga duración.
See Also
- Testing de Certificate Pinning en Aplicaciones Móviles: Validación SSL/TLS, Protección MITM y Rotación de Pins
- Requestly: Intercepción y Modificación de Solicitudes HTTP - Intercepción HTTP con Requestly: modificar headers, redirigir URLs, mock…
- Prueba certificate pinning: validación SSL/TLS, protección MITM,…
- API Contract Testing para Aplicaciones Móviles: Pact, Spring Cloud Contract y Mejores Prácticas - Contract testing para apps móviles: Pact, Spring Cloud Contract,…
- Estrategia de Versionado de API para Clientes Móviles: Compatibilidad Retroactiva, Actualizaciones Forzadas y A/B Testing - Versionado API para clientes móviles: compatibilidad retroactiva,…
- Appium 2.0: Nueva Arquitectura e Integración en la Nube para Testing Mobile Moderno - Explora la arquitectura revolucionaria de Appium 2.0, su…
