La autenticación es crítica para aplicaciones móviles. Probar flujos OAuth 2.0 y gestión de tokens JWT asegura experiencias de usuario seguras y fluidas. Esta guía cubre testing de flujos de autenticación, ciclo de vida de tokens, integración biométrica y mejores prácticas de seguridad.
Para estrategias completas de seguridad móvil más allá de autenticación, consulta nuestra guía de Testing de Seguridad Móvil.
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)
}
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.