Saltar al contenido principal
Móvil Seguridad

Mejores prácticas para el almacenamiento de tokens en apps móviles (2026)

Buenas prácticas para el almacenamiento de tokens: access tokens en memoria, refresh tokens en Keychain o Keystore, rotación y migración desde EncryptedSharedPreferences.

Cómo almacenar de forma segura tokens de acceso y refresco en móvil con Keychain y Keystore

La forma más segura de almacenar tokens de acceso en una app moderna es no persistirlos: mantén el token de acceso de vida corta en memoria durante la sesión y genera uno nuevo en cada inicio a partir de un token de refresco almacenado en almacenamiento cifrado respaldado por la plataforma. Persiste solo el token de refresco en iOS en el Keychain y en Android con Android Keystore (software, TEE o StrongBox, según el dispositivo) más Jetpack DataStore + Tink. EncryptedSharedPreferences está obsoleto en androidx.security.crypto; planea las migraciones ya. Para Capacitor apps, combina la seguridad de transporte (HTTPS, herramientas y plugins de SSL pinning) con plugins de almacenamiento con respaldo nativo y @capgo/capacitor-native-biometric en lugar de duplicar la criptografía en JavaScript.

Cómo elegimos estos patrones de almacenamiento

Cada recomendación aquí pasa cuatro filtros. Primero, almacenamiento protegido por la plataforma: los secretos deben residir en Keychain o Keystore, o estar cifrados con claves almacenadas allí. Prefiere el aislamiento respaldado por hardware cuando el dispositivo y la configuración de claves realmente lo proporcionen (por ejemplo, claves respaldadas por StrongBox o Secure Enclave). Segundo, una ruta de migración oficialmente soportada. Si Google o Apple han deprecado el enfoque, no lo recomendamos para nuevas code. Tercero, compatibilidad con Capacitor: el patrón debe funcionar desde el límite de un plugin sin exponer claves en bruto a JavaScript. Cuarto, revocabilidad: una credencial filtrada debe poder invalidarse en el servidor.

Ese último filtro es por qué la rotación de tokens de refresco aparece a lo largo de este artículo. Chainalysis informó que se robaron más de 305 millones de dólares de DMM Bitcoin en Japón tras la comprometer una clave privada en 2024 (informe de criminalidad de Chainalysis 2024). Los tokens de auth móvil fallan de la misma forma: un bearer token de larga duración en almacenamiento en texto plano es una invitación abierta. Dejaríamos de recomendar cualquier arquitectura que mantenga indefinidamente una credencial sin rotación.

Principios aplicables a cada plataforma

La tabla siguiente se comprime en instantáneas; la lógica subyacente es la separación de tokens de acceso/refresh. Un token de acceso es de vida corta, minutos y no días, y es lo que tu API presenta. Dado que su radio de impacto es pequeño y expira rápidamente, debe residir solo en memoria y reconstruirse en cada inicio de la app. El refrescar token es el secreto duradero que te permite emitir nuevos tokens de acceso, por lo que solo él merece un lugar en almacenamiento cifrado persistente con respaldo de hardware. Diferentes tiempos de vida justifican diferentes ubicaciones; almacenar ambos de la misma manera es donde comienzan la mayoría de las fugas.

La secuencia completa en tiempo de ejecución es así: la app se inicia sin token de acceso, llama a tu servidor de autenticación con el token de refresco persistido, recibe un nuevo token de acceso en memoria y destruye el token de refresco anterior porque el servidor lo rota en cada uso. Si el servidor ve que se presenta de nuevo un token de refresco ya consumido, lo trata como evidencia de robo y revoca toda la familia de tokens. Al cerrar sesión, el cliente borra el almacenamiento local y el servidor revoca la concesión. Borrar datos localmente sin revocación del servidor es esperar, no seguridad.

Práctica Por qué es importante
Tokens de acceso de vida corta Limita el daño si un token se fuga
Rotación de tokens de refresco Invalida tokens de refresco robados tras un uso legítimo
Solo HTTPS Detiene la interceptación casual de red
Certificate pinning (cuando está justificado) Reduce el riesgo de raíces mal emitidas o personalizadas; consulta la guía de certificate pinning de OWASP
Sin tokens en logs, URLs o analíticas Tokens in observability tools become breaches
Revocación al cerrar sesión Invalidar en el servidor es mejor que esperar a que el cliente borre los datos

@capgo/capacitor-ssl-pinning puedes fijar TLS para Capacitor HTTP en iOS y Android cuando necesitas controles de transporte más estrictos. Para una visión más amplia del endurecimiento del transporte, consulta Herramientas y plugins para implementar SSL pinning.

Android: Keystore y DataStore (no EncryptedSharedPreferences)

Google descontinuó el androidx.security.crypto helpers, incluidos EncryptedSharedPreferences y MasterKey. El resumen del paquete indica a los desarrolladores que usen KeyGenerator con el proveedor AndroidKeyStore para nuevo material de clave.

Usa un pipeline basado en Tink de extremo a extremo:

  1. Crea un keyset de Tink con AndroidKeysetManager, guardando la clave maestra en Android Keystore mediante un android-keystore:// URI (consulta las datastore-tink notas de la versión). Llama isUsingKeystore() al manager y falla cerrado cuando devuelve false. Tink puede almacenar el conjunto de claves en texto plano.
  2. Obtén un Aead primitiva de ese conjunto de claves.
  3. Añadir androidx.datastore:datastore-tink (DataStore 1.3.0 incluye el artefacto; 1.2.x no), envuelve tu Serializer con AeadSerializer, y pásalo a DataStore. Cuando varios almacenes comparten una Aead clave, asigna a cada almacén una associatedDataúnica, como un valor estable derivado de su nombre de archivo.

Google documenta esto como la ruta de reemplazo soportada para las funciones obsoletas androidx.security.crypto helpers. No mezcles un JCA independiente SecretKey con AeadSerializer; el serializer espera un Tink Aead instance.

Opciones de Keystore que conviene conocer

  • setIsStrongBoxBacked(true) cuando FEATURE_STRONGBOX_KEYSTORE está disponible para un aislamiento de claves más fuerte.
  • Autenticación de usuario binding (setUserAuthenticationRequired) cuando los tokens deben ser accesibles solo tras biometría o credencial del dispositivo.
  • Evita Auto Backup para archivos que contengan keysets o ciphertext, a menos que entiendas el comportamiento de restauración; la documentación de EncryptedSharedPreferences de Google advirtió de que el backup restaura ciphertext sin las claves.

Los módulos nativos de Kotlin o Java en Capacitor deben implementar este patrón dentro del plugin; JavaScript solo debería recibir handles opacos o estado de sesión, no claves en bruto.

iOS: Keychain y Secure Enclave

El Keychain sigue siendo el lugar correcto para los tokens en iOS. Los elementos están cifrados y protegidos por el sistema. Usa una constante de accesibilidad de alcance limitado y Control de acceso cuando necesites Face ID, Touch ID o protección con passcode.

La elección de accesibilidad es una decisión real, no un valor por defecto para copiar. Elige kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly cuando los tokens deben poder usarse en tareas en segundo plano, como refrescos activados por push o background fetch, pero nunca deben migrar a un nuevo dispositivo mediante backup. Elige kSecAttrAccessibleWhenUnlockedThisDeviceOnly para usar tokens solo en primer plano, donde la ventana más estricta justifica la restricción. En ambos casos, habilita kSecUseDataProtectionKeychain en targets modernos para obtener el Keychain de protección de datos en lugar del heredado basado en archivos.

CryptoKit ayuda cuando cifras los payloads tú mismo, pero para tokens bearer estándar tipo OAuth, guardar la cadena del token directamente en Keychain con la accesibilidad correcta suele bastar. Las claves respaldadas por Secure Enclave (una clave P-256 generada con SecureEnclave en CryptoKit) solo merecen la pena cuando la clave en sí nunca debe salir del hardware seguro, por ejemplo, para operaciones de firma o atestación de dispositivo. No pueden guardar una cadena de token por ti; protegen tu propia clave de envelope. Si no estás rotando envelopes y gestionando ese ciclo de vida, el almacenamiento simple en Keychain es más sencillo e igual de seguro. El cifrado adicional a nivel de app solo ayuda si también rotas claves y gestionas los envelopes con cuidado.

de Capacitor @capacitor/preferences escribe en UserDefaults, que es necesitas almacenamiento no seguro. No pongas tokens ahí.

Capacitor: opciones de almacenamiento seguro en la organización Cap-go

Cap-go hace necesitas lanzar un paquete llamado “secure storage”, pero dos plugins cubren las necesidades comunes de Capacitor:

Plugin Ideal para Base nativa
@capgo/capacitor-native-biometric setData / getSecureData Secretos pequeños (tokens de sesión, claves API) iOS Keychain; cifrado en SharedPreferences con Android Keystore
@capgo/capacitor-data-storage-sqlite con opciones de cifrado Datos locales grandes o estructurados SQLite con SQLCipher opcional

El guía de almacenamiento key-value compara Preferences, almacenes SQLite y NativeBiometric.setData para secretos.

Requiere @capgo/capacitor-native-biometric 8.6.0 o superior (ajusta la versión mayor del plugin a la de tu Capacitor. Ejemplo con lecturas con biometría:

import {
  AccessControl,
  NativeBiometric,
} from '@capgo/capacitor-native-biometric';

const TOKEN_KEY = 'auth.refresh_token';

export async function saveRefreshToken(token: string) {
  await NativeBiometric.setData({
    key: TOKEN_KEY,
    value: token,
    accessControl: AccessControl.BIOMETRY_CURRENT_SET,
  });
}

export async function loadRefreshToken(): Promise<string | null> {
  try {
    const { value } = await NativeBiometric.getSecureData({
      key: TOKEN_KEY,
      reason: 'Unlock your session',
      title: 'Authenticate',
    });
    return value ?? null;
  } catch {
    return null;
  }
}

getData solo devuelve valores almacenados sin accessControl. Para protección biométrica, combina siempre BIOMETRY_CURRENT_SET o BIOMETRY_ANY con getSecureData.

Para web builds, nunca guardes tokens de producción en localStorage. Usa mejor el modelo de sesión web de tu backend.

Ciclo de vida del token en el servidor y el cliente

Sí, las apps móviles deben almacenar tokens de refresco. La alternativa es obligar a los usuarios a reautenticarse en cada inicio, lo cual es peor: teatro de seguridad en lugar de seguridad real. El matiz es dónde y cómo: persiste solo el refresh token, solo en almacenamiento respaldado por Keychain o Keystore, y solo con rotación y revocación en el servidor.

Cómo gestionar y rotar de forma segura los tokens de refresco en una app móvil

La rotación es lo que hace segura la persistencia. Emite refresh tokens como de un solo uso : cada canje devuelve un nuevo refresh token e invalida el anterior. El servidor mantiene una breve ventana de detección de reutilización. Si un token supuestamente consumido se presenta de nuevo, el sistema trata la familia como comprometida y revoca toda la cadena, forzando un inicio de sesión nuevo en cada dispositivo que lo compartió. Permite un pequeño margen de gracia (de unos segundos a un minuto) para solicitudes simultáneas y reconexiones offline, donde un cliente legítimo puede presentar el token antiguo una última vez antes de conocer el reemplazo. No tener margen de gracia bloquea a usuarios con conexiones inestables; una ventana demasiado larga amplía la superficie de ataque.

La ubicación de cada token depende directamente de su tiempo de vida:

  • Problema tokens de acceso con TTL cortos (minutos, no días), y mantenlos solo en memoria durante la sesión.
  • Persiste el refrescar token en Keychain o en almacenamiento cifrado respaldado por Keystore, y rótalo como se indicó.
  • Al cambiar la contraseña, cerrar sesión o perder el dispositivo, revoca tokens de refresco en el servidor. Que el cliente elimine datos locales no es una revocación.
  • Considera una estrategia de token binding ligera (device key attestation o app instance IDs) si tu modelo de amenazas lo requiere.

Al lanzar parches de seguridad para el manejo de tokens en apps de Capacitor, actualizaciones Live Update de Capgo puede entregar cambios elegibles de la capa web rápidamente si cumples las políticas de las tiendas. Los cambios en el almacenamiento nativo siguen requiriendo un build de tienda. Capgo resumen de seguridad resume cómo las aplicaciones de producción combinan controles de transporte, almacenamiento y actualizaciones.

Comparativa de opciones de storage

Método Seguridad Compatibilidad con Capacitor
Variable JS en memoria Alta en ejecución, se pierde al matar la app OK solo para el access token durante la sesión
localStorage / Preferencias Bajo Solo ajustes, nunca secretos
Keychain de iOS / Keystore de Android Alto Vía code nativo o plugins de Capgo
DataStore + Tink (nativo de Android) Alto Plugin Capacitor personalizado o SDK de autenticación nativa
Cifrado personalizado en JS Variable Evitar; las claves en JS son extraíbles

Vale la pena señalar en esa última fila: el modo de fallo del cifrado de JavaScript no son algoritmos débiles, sino la ubicación. Cualquier clave que un runtime de JS pueda leer puede ser exfiltrada por lo que sea que esté en el runtime, por lo que la "alta seguridad" es estructuralmente inalcanzable desde esa capa, sin importar qué biblioteca elijas.

Lista de verificación para migrar desde EncryptedSharedPreferences

  1. Inventariar qué claves contienen secretos y cuáles son configuración pública.
  2. Implementar DataStore + AeadSerializer con un keyset de Tink respaldado por Keystore.
  3. Migrar con SharedPreferencesMigration usando un producer que abra el legacy EncryptedSharedPreferences instancia (no plain SharedPreferences), luego elimina los archivos obsoletos tras una lectura exitosa.
  4. Publica el cambio nativo en una versión de la tienda; prueba la copia de seguridad y la restauración en un dispositivo físico.
  5. Eliminar androidx.security.crypto dependencias cuando nada las referencia.

FAQ

¿Cómo se implementa el almacenamiento seguro de tokens?

En iOS, escribe los refresh tokens en el Keychain con un nivel de accesibilidad que se ajuste a las necesidades de actualización en segundo plano. En Android, usa un keyset de Tink en Android Keystore con Jetpack DataStore y AeadSerializer en lugar de EncryptedSharedPreferences obsoletas. Mantén los tokens de acceso solo en memoria y usa HTTPS. Añade certificate pinning cuando tu modelo de amenazas lo requiera.

¿Cómo gestiono y roto de forma segura los refresh tokens en una app móvil?

Almacena el refresh token solo en almacenamiento respaldado por Keychain o Keystore. Emite refresh tokens de un solo uso para que cada canje devuelva un nuevo refresh token e invalide el anterior. Mantén una ventana corta de detección de reutilización para que un token reproducido revoque toda la familia, con un pequeño margen de tolerancia para clientes sin conexión o en carrera.

¿El almacenamiento seguro de tokens de acceso y de actualización es el mismo en iOS y Android?

La división es la misma en ambas plataformas: memoria para tokens de acceso de vida corta y almacenamiento cifrado protegido por la plataforma para tokens de actualización. Las APIs difieren (Keychain frente a Keystore más DataStore), pero ambas deben evitar WebView localStorage, UserDefaults y SharedPreferences planos para secretos.

¿Debo cifrar los tokens antes de guardarlos en Keychain?

Normalmente no se requiere una capa extra para tokens bearer estándar si la accesibilidad de Keychain coincide con tu modelo de amenazas. Añade cifrado de sobre cuando las regulaciones o las políticas de rotación de claves lo exijan.

¿SQLCipher en un plugin de Capacitor reemplaza a Keystore?

SQLCipher protege el archivo de base de datos, pero aún necesitas una clave fuerte en Keystore o Keychain para descifrarla. Trata el manejo de la frase de cifrado como parte de tu diseño de almacenamiento seguro.

¿Puedo almacenar tokens en Capacitor Preferences con ofuscación?

La ofuscación no es cifrado. Preferences se mapea a UserDefaults y SharedPreferences sin protección respaldada por hardware.

¿Deben las apps móviles almacenar tokens de actualización?

Sí, pero solo en almacenamiento cifrado respaldado por Keychain o Keystore, junto con rotación y revocación en el servidor. No almacenarlos implica reautenticarse en cada inicio, lo que los usuarios evitan exigiendo requisitos de inicio de sesión más débiles. Lo que nunca es aceptable es el token de actualización en Preferences, localStorage o cualquier archivo en texto plano.

¿Cuál es la forma más segura de almacenar tokens de acceso en una app moderna?

No los persistas en absoluto. Mantén el token de acceso de vida corta en memoria durante la sesión y reconstrúyelo desde el refresh token en cada inicio. Persiste solo el refresh token en almacenamiento cifrado respaldado por iOS Keychain o Android Keystore, y deja que la rotación y la revocación limiten el daño si un dispositivo se ve comprometido.

Continuar desde Almacenamiento seguro de tokens: buenas prácticas para desarrolladores móviles

Si estás usando Almacenamiento seguro de tokens: buenas prácticas para desarrolladores móviles para planificar la seguridad y el cumplimiento, conéctalo con Cifrado para los detalles de implementación en Cifrado, Cumplimiento para los detalles de implementación en Cumplimiento, Escáner de seguridad de Capgo para el flujo de trabajo del producto en Capgo Security Scanner, Seguridad de Capgo para el flujo de trabajo del producto en Capgo Security, y Centro de confianza de Capgo para el flujo de trabajo del producto en Capgo Trust Center.

Live updates para apps Capacitor

Cuando un bug de la capa web está en producción, publica el fix a través de Capgo en lugar de esperar días a la aprobación de la app store. Los usuarios reciben la actualización en segundo plano mientras los cambios nativos siguen el flujo de revisión habitual.

Lo último de nuestro blog

Capgo te da la información clave que necesitas para crear una app móvil realmente profesional.