Speichere Auth-Token in plattformgestütztem, verschlüsseltem Storage statt im WebView localStorage oder einfachen SharedPreferences. Nutze unter iOS den Keychain. Unter Android schützt du Keys mit Android Keystore (Software, TEE oder StrongBox, je nach Gerät) und speichere den Ciphertext mit Jetpack DataStore + Tink. EncryptedSharedPreferences ist veraltet in androidx.security.crypto; plane jetzt Migrationen. Für Capacitor Apps solltest du Transport-Security (HTTPS, Pinning) mit nativen Storage-Plugins kombinieren, statt Krypto-Logik in JavaScript zu duplizieren.
So haben wir diese Storage-Patterns ausgewählt
Jede Empfehlung hier durchläuft vier Filter. Erstens, Hardware-Backing: Das Secret muss entweder im Keychain/Keystore liegen oder mit Schlüsseln verschlüsselt sein, die dort liegen. Zweitens, ein offiziell unterstützter Migrationspfad – wenn Google oder Apple einen Ansatz als deprecated markiert haben, empfehlen wir ihn nicht für neue code. Drittens, Capacitor Kompatibilität: Das Pattern muss über die Plugin-Grenze funktionieren, ohne Rohschlüssel an JavaScript zu exponieren. Viertens, Revokierbarkeit: Ein geleaktes Credential muss serverseitig ungültig gemacht werden können.
Dieser letzte Filter ist der Grund, warum Refresh Token Rotation in diesem Artikel immer wieder vorkommt. In unserer Analyse der von Chainalysis zusammengestellten Vorfälle aus 2024 waren Kompromisse bei Private Keys und Credentials für den Großteil der gestohlenen Kryptowährungen verantwortlich – darunter über 305 Mio. $ von Japans DMM Bitcoin und 235 Mio. $ von Indiens WazirX – und das häufigste Problem war blindes Vertrauen in ein einziges, dauerhaft gültiges Credential (Chainalysis 2024 Kriminalitätsbericht). Mobile Auth-Tokens scheitern auf dieselbe Weise: Ein langlebiger Bearer-Token in Plaintext-Speicherung ist eine offene Einladung. Wir empfehlen keine Architektur mehr, die ein nicht rotierendes Credential unbegrenzt speichert.
Prinzipien für alle Plattformen
Die folgende Tabelle fasst dies in Snapshots zusammen; die zugrunde liegende Logik ist die Aufteilung in Access- und Refresh-Token. Ein Access Token ist kurzlebig – Minuten, keine Tage – und ist das, was deine API-Aufrufe vorlegen. Da der Blast Radius klein ist und er schnell abläuft, gehört er nur in den Arbeitsspeicher und wird bei jedem App-Start neu erzeugt. Der Refresh Token ist das dauerhafte Geheimnis, mit dem du neue Access Tokens erzeugst, deshalb gehört er allein in persistenten, hardwaregestützten und verschlüsselten Speicher. Unterschiedliche Lebensdauern rechtfertigen unterschiedliche Speicherorte; beide gleich abzulegen, ist der Anfang der meisten Leaks.
Der vollständige Ablauf zur Laufzeit sieht so aus: Die App startet ohne Access Token, ruft deinen Auth-Server mit dem gespeicherten Refresh Token auf, erhält einen neuen Access Token im Arbeitsspeicher und verwirft den alten Refresh Token, weil der Server ihn bei jeder Nutzung rotiert. Sieht der Server einen bereits verbrauchten Refresh Token erneut, wertet er das als Diebstahl und widerruft die gesamte Token-Familie. Beim Logout löscht der Client den lokalen Speicher und der Server widerruft das Grant – Daten lokal zu löschen, ohne serverseitigen Widerruf, ist Hoffen statt Sicherheit.
| Best Practices | Warum das wichtig ist |
|---|---|
| Kurzlebige Access Tokens | Begrenzt den Schaden bei einem Token-Leak |
| Refresh Token Rotation | Macht gestohlene Refresh Tokens nach legitimer Nutzung ungültig |
| Nur HTTPS | Verhindert einfaches Abfangen im Netzwerk |
| Certificate Pinning (wenn gerechtfertigt) | Reduziert Risiken durch fehlerhaft ausgestellte oder eigene Root-Zertifikate; siehe OWASP-Empfehlungen zu Certificate Pinning |
| Keine Tokens in Logs, URLs oder Analytics | Tokens in Observability-Tools werden zu Breaches |
| Widerruf beim Logout | Serverseitige Invalidierung ist besser als die Hoffnung, dass der Client Daten löscht |
@capgo/capacitor-ssl-pinning kann TLS für Capacitor HTTP unter iOS und Android pinnen, wenn Sie strengere Transportkontrollen brauchen.
Android: Keystore und DataStore (nicht EncryptedSharedPreferences)
Google hat den folgenden Dienst eingestellt: androidx.security.crypto Helper, darunter EncryptedSharedPreferences und MasterKey. Die Paketübersicht empfiehlt Entwicklern die Nutzung von KeyGenerator mit dem AndroidKeyStore-Provider für neues Key-Material.
Empfohlenes Pattern
Nutze eine durchgängige Tink-basierte Pipeline:
- Erstelle ein Tink-Keyset mit
AndroidKeysetManager, wobei der Master Key im Android Keystore über einenandroid-keystore://URI (siehe diedatastore-tinkRelease Notes). - Erhalte einen
AeadPrimitive aus diesem Keyset. - Füge
androidx.datastore:datastore-tink(verfügbar ab DataStore 1.3.0-rc01; die stabile 1.2.x Version enthält diesen Artifact nicht), umschließe deinenSerializermitAeadSerializer, und übergib es anDataStore. Wenn mehrere Stores einen gemeinsamenAeadKey nutzen, gib jedem Store einen eindeutigenassociatedData, wie etwa einen stabilen Wert aus dem Dateinamen.
Google dokumentiert dies als unterstützten Ersatzpfad für veraltete androidx.security.crypto Helpers. Mische keinen eigenständigen JCA SecretKey mit AeadSerializer; der Serialisierer erwartet eine Tink- Aead Instanz.
Ein Hinweis zur Versionierung: Da das datastore-tink Artefakt erst ab 1.3.0-rc01 existiert, werden Teams, die auf stabilen 1.2.x-Releases festgeschrieben sind, den dokumentierten Ersatz in ihrem Dependency Tree nicht finden. Diese Lücke besteht bereits seit mehreren Releases, und unserer Meinung nach ist das der Hauptgrund, warum Migrationspläne stagnieren – entscheidet bewusst, ob ihr einen Release Candidate nutzt oder wartet, anstatt es dem Zufall zu überlassen.
Wichtige Keystore-Optionen
setIsStrongBoxBacked(true)wennFEATURE_STRONGBOX_KEYSTOREbietet eine stärkere Key-Isolation.- Benutzerauthentifizierung Binding (
setUserAuthenticationRequired) wenn Tokens erst nach Biometrie oder Geräte-Credentials zugänglich sein sollen. - Auto Backup vermeiden für Dateien mit Keysets oder Ciphertext, sofern das Restore-Verhalten nicht genau bekannt ist; die Google EncryptedSharedPreferences-Docs warnen davor, dass Backups Ciphertext ohne die passenden Keys wiederherstellen.
Native Kotlin- oder Java-Module in Capacitor sollten dieses Pattern innerhalb des Plugins implementieren; JavaScript sollte nur opaque Handles oder den Session-State erhalten, keine Rohschlüssel.
iOS: Keychain und Secure Enclave
Die Keychain ist weiterhin der richtige Ort für Tokens unter iOS. Items werden verschlüsselt und vom System geschützt. Nutze eine eng gefasste Accessibility-Konstante und Zugriffskontrolle wenn Face ID, Touch ID oder ein Passcode als Gate benötigt werden.
Die Wahl der Accessibility ist eine echte Design-Entscheidung, kein Standard zum Kopieren. Wähle kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly wenn Tokens für Background-Work nutzbar sein müssen – wie Push-triggered Refreshes oder Background Fetch – aber niemals via Backup auf ein neues Gerät migriert werden dürfen. Wähle kSecAttrAccessibleWhenUnlockedThisDeviceOnly für die reine Nutzung im Vordergrund, wenn die striktere Einschränkung den Vorteil rechtfertigt. Aktiviere in beiden Fällen kSecUseDataProtectionKeychain auf modernen Zielgeräten, um den Data-Protection Keychain statt der veralteten file-basierten Variante zu nutzen.
CryptoKit hilft, wenn du Payloads selbst verschlüsselst. Bei Standard-OAuth-Bearer-Tokens reicht es meist aus, den Token-String direkt im Keychain mit der richtigen Accessibility zu speichern. Secure Enclave-basierte Keys (ein P-256 Key, generiert mit SecureEnclave in CryptoKit) lohnen sich nur, wenn der Key selbst niemals die sichere Hardware verlassen darf – etwa bei Signing-Operationen oder Device Attestation. Sie können keinen Token-String für dich halten; sie schützen deinen eigenen Envelope-Key. Wenn du keine Envelopes rotierst und deren Lifecycle nicht verwaltest, ist die einfache Keychain-Speicherung unkomplizierter und nicht weniger sicher. Zusätzliche Verschlüsselung auf App-Ebene hilft nur, wenn du auch Keys rotierst und Envelopes sorgfältig verwaltest.
Capacitor’s @capacitor/preferences schreibt in UserDefaults, was kein nicht sicherer Speicher ist. Speichere dort keine Tokens.
Capacitor: Sichere Speicheroptionen in der Cap-go Org
Cap-go nutzt nicht ein Paket namens „Secure Storage“ aus, aber zwei Plugins decken gängige Capacitor Anforderungen ab:
| Plugin | Am besten für | Native Absicherung |
|---|---|---|
@capgo/capacitor-native-biometric setData / getSecureData |
Kleine Secrets (Session-Tokens, API Keys) | iOS Keychain; Android Keystore-basierte Verschlüsselung in SharedPreferences |
| @capgo/capacitor-data-storage-sqlite mit Verschlüsselungsoptionen | Größere oder strukturierte lokale Daten | SQLite mit optionalem SQLCipher |
Die Leitfaden für Key-Value-Storage vergleicht Preferences, SQLite-Stores und NativeBiometric.setData für Secrets.
Erfordert @capgo/capacitor-native-biometric 8.6.0 oder neuer (Passe die Major-Version des Plugins an deine Capacitor Major-Version an). Beispiel mit biometrisch abgesicherten Lesezugriffen:
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 gibt nur gespeicherte Werte zurück ohne accessControl. Für biometrischen Schutz immer kombinieren mit BIOMETRY_CURRENT_SET oder BIOMETRY_ANY mit getSecureData.
Für Web-Builds niemals Production-Tokens in localStorage. Nutze stattdessen das Web-Session-Modell deines Backends.
Token-Lifecycle auf Server und Client
Ja, mobile Apps sollten Refresh Tokens speichern – die Alternative wäre, Nutzer bei jedem App-Start zur Re-Authentifizierung zu zwingen, was eher Security Theater als echte Sicherheit ist. Die Nuance ist: wo und wie: Speichere nur das Refresh Token, ausschließlich in Keychain- oder Keystore-gestütztem Storage, und nur mit Rotation und serverseitiger Revocation dahinter.
Rotation macht die Persistenz sicher. Gib Refresh Tokens als Single-Use Credentials aus: Jede Einlösung liefert ein neues Refresh Token und invalidiert das alte. Der Server hält ein kurzes Fenster zur Reuse-Detection — wird ein vermeintlich verbrauchtes Token erneut vorgelegt, behandelt der Server die Token-Familie als kompromittiert und widerruft die gesamte Kette. Das erzwingt einen neuen Login auf jedem Gerät, das sie geteilt hat. Erlaube eine kurze Grace Period (wenige Sekunden bis eine Minute) für konkurrierende Requests und Offline-Reconnects, in der ein legitimer Client das alte Token ein letztes Mal senden darf, bevor er vom Ersatz-Token erfährt. Ohne Grace Period werden Nutzer bei instabilen Verbindungen ausgesperrt; ein zu langes Fenster vergrößert die Angriffsfläche.
Der Speicherort eines Tokens hängt direkt von seiner Lebensdauer ab:
- Issue Access Tokens mit kurzen TTLs (Minuten, nicht Tage) und halten Sie diese nur im Arbeitsspeicher während der Session.
- Speichern Sie Refresh Token im Keychain oder Keystore-basierten verschlüsselten Speicher und rotieren Sie ihn wie oben beschrieben.
- Bei Passwortänderung, Logout oder Geräteverlust: entziehen Sie Refresh Tokens serverseitig – das Löschen lokaler Daten durch den Client ist keine Revocation.
- Erwäge eine leichtgewichtige Token-Binding- Strategie (Device Key Attestation oder App Instance IDs), falls dein Bedrohungsmodell dies erfordert.
Wenn du Sicherheitsfixes für das Token-Handling in Capacitor-Apps ausrollst, Capgo Live Updates ermöglicht die schnelle Bereitstellung berechtigter Web-Layer-Änderungen, sofern die Store-Richtlinien eingehalten werden. Änderungen am Native Storage erfordern weiterhin einen Store-Build.
Storage-Optionen im Vergleich
| Methode | Sicherheit | Fit für Capacitor |
|---|---|---|
| In-Memory-JS-Variable | Hoch während der Laufzeit, beim Kill verloren | OK für Access-Token nur während der Session |
localStorage / Einstellungen |
Niedrig | Nur Einstellungen, niemals Secrets |
| iOS Keychain / Android Keystore | Hoch | Über natives code oder Capgo-Plugins |
| DataStore + Tink (Android native) | Hoch | Custom-Capacitor-Plugin oder native Auth-SDK |
| Custom Crypto in JS | Variable | Vermeiden; Keys in JS sind extrahierbar |
Wichtig zur letzten Zeile: Das Problem bei JavaScript-Crypto sind nicht schwache Algorithmen, sondern der Speicherort. Jeder Key, den eine JS-Runtime lesen kann, kann auch von allem anderen in der Runtime exfiltriert werden. „Hohe Sicherheit“ ist auf dieser Ebene strukturell unerreichbar, egal welche Library man wählt.
Migrations-Checkliste von EncryptedSharedPreferences
- Inventur: Welche Keys halten Secrets vs. öffentliche Configs.
- Implementiere DataStore +
AeadSerializermit einem Keystore-basierten Tink Keyset. - Migriere mit
SharedPreferencesMigrationunter Verwendung eines Producers, der die Legacy- EncryptedSharedPreferences Instanz öffnet (nicht im KlartextSharedPreferences), dann lösche die Legacy-Files nach erfolgreichem Read. - Release der nativen Änderung über den Store; Backup und Restore auf einem physischen Gerät testen.
- Entfernen
androidx.security.cryptoDependencies, wenn keine Referenzen mehr darauf bestehen.
FAQ
Sollte ich Tokens verschlüsseln, bevor ich sie im Keychain speichere?
Normalerweise ist für Standard-Bearer-Tokens keine zusätzliche Ebene erforderlich, sofern die Keychain-Zugriffsrechte deinem Threat Model entsprechen. Nutze Envelope Encryption, wenn regulatorische Vorgaben oder Key-Rotation-Policies dies erfordern.
Ersetzt SQLCipher in einem Capacitor Plugin den Keystore?
SQLCipher schützt die Datenbankdatei, aber du benötigst weiterhin einen starken Key im Keystore oder Keychain, um die Datenbank zu entschlüsseln. Behandle das Handling von Encryption Passphrases als integralen Bestandteil deines Secure-Storage-Designs.
Kann ich Tokens mit Obfuscation in Capacitor Preferences speichern?
Obfuscation ist keine Verschlüsselung. Preferences mappt auf UserDefaults und SharedPreferences, was ohne Hardware-Schutz erfolgt.
Sollten mobile Apps Refresh Tokens speichern?
Ja – aber nur im Keychain oder in einem Keystore-basierten verschlüsselten Speicher, kombiniert mit serverseitiger Rotation und Revocation. Sie nicht zu speichern bedeutet Re-Authentication bei jedem App-Start, was Nutzer dazu verleitet, schwächere Login-Anforderungen zu fordern. Was niemals akzeptabel ist: Refresh Tokens in Preferences, localStorage oder irgendwelchen Plaintext-Dateien zu speichern.
Was ist der sicherste Weg, Access Tokens in einer modernen App zu speichern?
Speichere sie gar nicht erst dauerhaft. Behalte den kurzlebigen Access Token während der Session im Arbeitsspeicher und generiere ihn bei jedem App-Start aus dem Refresh Token neu. Speichere nur den Refresh Token – im iOS Keychain oder in einem durch Android Keystore abgesicherten verschlüsselten Speicher – und nutze Token-Rotation sowie Revocation, um den Schaden bei einem kompromittierten Gerät zu begrenzen.
Weiterlesen unter Secure Token Storage: Best Practices for Mobile Developers
Wenn Sie ... verwenden Sichere Token-Speicherung: Best Practices für Mobile Developer nutzen, um Security und Compliance zu planen, verbinden Sie es mit Verschlüsselung für die Implementierungsdetails bei der Verschlüsselung, Compliance für die Implementierungsdetails in Compliance, Capgo Security Scanner für den Produkt-Workflow im Capgo Security Scanner, Capgo Sicherheit für den Produkt-Workflow in Capgo Security und Capgo Trust Center für den Produkt-Workflow im Capgo Trust Center.