Der sicherste Weg, Access-Tokens in einer modernen App zu speichern, ist, sie nicht zu persistieren: Halte das kurzlebige Access-Token während der Sitzung im Speicher und erzeuge bei jedem Start ein neues aus einem Refresh-Token, das in einem vom Betriebssystem bereitgestellten verschlüsselten Speicher liegt. Persistiere auf iOS nur das Refresh-Token im Keychain und auf Android mit Android Keystore verschlüsselt (Software, TEE oder StrongBox, je nach Gerät) sowie Jetpack DataStore + Tink. EncryptedSharedPreferences ist veraltet in einem androidx.security.crypto; plane jetzt Migrationen. Für Capacitor Apps, kombiniere Transportsicherheit (HTTPS, SSL-Pinning-Tools und Plugins) mit Storage-Plugins mit nativer Anbindung und @capgo/capacitor-native-biometric statt Kryptografie in JavaScript zu duplizieren.
Wie wir diese Storage-Patterns gewählt haben
Jede Empfehlung hier durchläuft vier Filter. Erstens: plattformgeschützter Speicher. Secrets müssen im Keychain oder Keystore liegen oder mit Schlüsseln verschlüsselt sein, die dort gespeichert sind. Bevorzuge hardwaregestützte Isolation, wenn Gerät und Schlüsselkonfiguration das tatsächlich bieten (z. B. StrongBox- oder Secure-Enclave-gestützte Schlüssel). Zweitens: ein offiziell unterstützter Migrationspfad. Wenn Google oder Apple den Ansatz als veraltet eingestuft hat, empfehlen wir ihn nicht für neue code. Drittens: Capacitor-Kompatibilität: Das Muster muss von einer Plugin-Grenze aus funktionieren, ohne rohe Schlüssel an JavaScript preiszugeben. Viertens: Widerrufbarkeit: Ein geleaktes Credential muss serverseitig ungültig gemacht werden können.
Dieser letzte Filter ist der Grund, warum Refresh-Token-Rotation in diesem Artikel durchgängig vorkommt. Chainalysis berichtete, dass bei Japans DMM Bitcoin nach einer Private-Key-Kompromittierung im Jahr 2024 mehr als 305 Millionen US-Dollar gestohlen wurden (Chainalysis-Bericht zur Kriminalität 2024). Mobile-Auth-Tokens scheitern auf dieselbe Weise: Ein langlebiges Bearer-Token im Plaintext-Storage ist eine offene Einladung. Wir würden keine Architektur mehr empfehlen, die ein nicht rotierendes Credential unbegrenzt speichert.
Prinzipien, die auf jeder Plattform gelten
Die folgende Tabelle fasst das in Snapshots zusammen; die zugrunde liegende Logik ist die Aufteilung in Access- und Refresh-Token. Ein Access Token ist kurzlebig, Minuten, nicht Tage, und ist das, was dein API als vorhanden bezeichnet. Da sein Blast Radius klein ist und er schnell abläuft, gehört er nur in den Speicher und wird bei jedem App-Start neu erstellt. Der Refresh Token ist das dauerhafte Geheimnis, mit dem du neue Access Tokens erzeugen kannst, deshalb gehört nur er in den persistenten, hardwaregestützten verschlüsselten Speicher. Unterschiedliche Lebensdauern rechtfertigen unterschiedliche Speicherorte; beide gleich zu speichern ist der Anfang der meisten Leaks.
Die vollständige Abfolge zur Laufzeit sieht so aus: Die App startet ohne Access Token, ruft deinen Auth-Server mit dem persistierten Refresh Token auf, erhält einen frischen Access Token im Speicher und zerstört den alten Refresh Token, da der Server ihn bei jeder Nutzung rotiert. Wenn der Server einen bereits verbrauchten Refresh Token erneut vorfindet, wertet er das als Diebstahlsbeweis und widerruft die gesamte Token-Familie. Beim Logout löscht der Client den lokalen Speicher und der Server widerruft den Grant. Daten lokal zu löschen, ohne sie serverseitig zu widerrufen, ist Hoffnung, keine Sicherheit.
| Maßnahme | 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 (falls gerechtfertigt) | Reduziert Risiken durch fehlerhaft ausgestellte oder eigene Root-Zertifikate; siehe OWASP-Leitfaden zu Certificate Pinning |
| Keine Tokens in Logs, URLs oder Analytics | Tokens in Observability-Tools werden zu Sicherheitsvorfällen |
| Widerruf beim Logout | Server-seitige Invalidation ist sicherer 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 du strengere Transportkontrollen brauchst. Für einen breiteren Blick auf Transport-Hardening siehe Tools und Plugins zur SSL-Pinning-Implementierung.
Android: Keystore und DataStore (nicht EncryptedSharedPreferences)
Google hat ... als veraltet markiert androidx.security.crypto Helper, darunter EncryptedSharedPreferences und MasterKey. Die Paketbeschreibung sagt Entwicklern, sie sollen nutzen KeyGenerator mit dem AndroidKeyStore-Provider für neues Key-Material.
Empfohlenes Pattern
Nutze eine durchgehende Tink-basierte Pipeline:
- Erstelle ein Tink-Keyset mit
AndroidKeysetManager, wobei der Master Key im Android Keystore über eineandroid-keystore://URI gespeichert wird (siehedatastore-tinkRelease Notes). RufeisUsingKeystore()am Manager und schließt bei einem Fehler ab, wenn er zurückgibtfalse. Tink kann den Keyset andernfalls im Klartext speichern. - Erhalte einen
AeadPrimitive aus diesem Keyset. - Füge
androidx.datastore:datastore-tink(DataStore 1.3.0 liefert das Artefakt; 1.2.x nicht), wickeln Sie IhrSerializermitAeadSerializer, und übergib es anDataStore. Wenn mehrere Stores einen gemeinsamenAeadKey nutzen, gib jedem Store einen eindeutigenassociatedData, z. B. einen stabilen Wert aus dem Dateinamen.
Google dokumentiert dies als den unterstützten Ersatzpfad für veraltete androidx.security.crypto Helpers. Mische keinen eigenständigen JCA SecretKey mit AeadSerializer; der Serializer erwartet eine Tink- Aead Instanz.
Wichtige Keystore-Optionen
setIsStrongBoxBacked(true)wennFEATURE_STRONGBOX_KEYSTOREist für eine stärkere Key-Isolation verfügbar.- Benutzerauthentifizierung Binding (
setUserAuthenticationRequired) wenn Tokens nur nach Biometrie oder Geräte-Credential zugänglich sein sollen. - Auto Backup vermeiden für Dateien, die Keysets oder Ciphertext enthalten, es sei denn, du verstehst das Restore-Verhalten; die Google EncryptedSharedPreferences-Docs warnen davor, dass Backups Ciphertext ohne 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 Raw Keys.
iOS: Keychain und Secure Enclave
Der 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 du Face ID, Touch ID oder Passcode-Gating benötigst.
Die Wahl der Accessibility ist eine echte Entscheidung, kein Standard zum Kopieren. Wähle kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly wenn Tokens von Hintergrundprozessen genutzt werden müssen, z. B. für durch Push ausgelöste Refreshes oder Background Fetch, aber niemals per Backup auf ein neues Gerät migriert werden dürfen. Auswählen kSecAttrAccessibleWhenUnlockedThisDeviceOnly für Token-Nutzung nur im Vordergrund, wenn das engere Zeitfenster die Einschränkung wert ist. Aktiviere in beiden Fällen kSecUseDataProtectionKeychain auf modernen Targets, damit du den Data-Protection-Keychain bekommst statt der Legacy-Variante auf Dateibasis.
CryptoKit hilft, wenn du Payloads selbst verschlüsselst, aber für Standard-OAuth-Bearer-Tokens reicht es in der Regel, den Token-String direkt im Keychain mit der richtigen Accessibility zu speichern. Secure-Enclave-gestützte Schlüssel (ein P-256-Schlüssel, generiert SecureEnclave in CryptoKit) lohnen sich nur, wenn der Schlüssel selbst die sichere Hardware nie verlassen darf, z. B. für Signieroperationen oder Device Attestation. Sie können keinen Token-String für dich halten; sie schützen deinen eigenen Envelope-Schlüssel. Wenn du keine Envelopes rotierst und deren Lifecycle nicht verwaltest, ist normale Keychain-Speicherung einfacher und genauso sicher. Zusätzliche App-Layer-Verschlüsselung hilft nur, wenn du auch Schlüssel rotierst und Envelopes sorgfältig verwaltest.
Capacitors @capacitor/preferences schreibt in UserDefaults, was es nicht kein sicherer Speicher ist. Leg keine Tokens dort ab.
Capacitor: Sichere Speicheroptionen in der Cap-go-Org
Cap-go macht das es nicht Zwar gibt es ein Paket namens „secure storage“, aber zwei Plugins decken die gängigen Capacitor-Anforderungen ab:
| Plugin | Ideal für | Native Grundlage |
|---|---|---|
@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 |
Der 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.
Bei Web-Builds niemals Production-Tokens in localStorage. Verwenden Sie stattdessen das Web-Session-Modell Ihres Backends.
Token-Lifecycle auf Server und Client
Ja, mobile Apps sollten Refresh-Tokens speichern. Die Alternative wäre, Nutzer bei jedem Start zur erneuten Authentifizierung zu zwingen – das ist schlechtere Sicherheitstheater als echte Sicherheit. Der Feinunterschied liegt in wo und der Frage: Nur das Refresh-Token persistieren, ausschließlich in Keychain- oder Keystore-basierter Speicherung und nur mit Rotation und serverseitiger Widerrufbarkeit im Hintergrund.
So verwalten und rotieren Sie Refresh-Tokens in einer mobilen App sicher
Rotation macht die Persistenz sicher. Stellen Sie Refresh-Tokens als Einmalnutzung credentials: Jede Einlösung liefert einen neuen Refresh-Token und invalidiert den alten. Der Server behält ein kurzes Wiederverwendungs-Fensterbei. Wird ein angeblich bereits verbrauchtes Token erneut vorgelegt, behandelt er die Token-Familie als kompromittiert und widerruft die gesamte Kette, was einen frischen Login auf jedem Gerät erzwingt, das es geteilt hat. Erlaube eine kleine Gnadenfrist (einige Sekunden bis eine Minute) für konkurrierende Anfragen und Offline-Reconnects, bei denen ein legitimer Client das alte Token ein letztes Mal vorlegen kann, bevor er von der Ersetzung erfährt. Ohne Gnadenfrist werden Nutzer bei instabilen Verbindungen ausgesperrt; ein zu langes Fenster vergrößert die Angriffsfläche.
Wo jedes Token gespeichert wird, ergibt sich direkt aus seiner Lebensdauer:
- Problem Access-Tokens mit kurzer TTL (Minuten, keine Tage) und halte sie nur im Speicher während der Sitzung.
- Persistiere den Refresh Token in Keychain- oder Keystore-gestütztem verschlüsseltem Speicher und rotiere ihn wie oben beschrieben.
- Bei Passwortänderung, Logout oder Geräteverlust widerrufen Refresh-Tokens serverseitig. Das Löschen lokaler Daten durch den Client ist kein Widerruf.
- Erwäge eine leichtgewichtige Token-Binding- Strategie (Device Key Attestation oder App Instance IDs), falls dein Threat Model dies erfordert.
Wenn du Security-Fixes für das Token-Handling in Capacitor Apps ausrollst, Capgo Live Updates können geeignete Web-Layer-Änderungen schnell ausliefern, solange Sie sich an die Store-Richtlinien halten. Änderungen am nativen Speicher erfordern weiterhin einen Store-Build. Capgo Sicherheitsübersicht fasst zusammen, wie Produktions-Apps Transport, Speicherung und Update-Steuerungen kombinieren.
Storage-Optionen im Vergleich
| Methode | Sicherheit | Capacitor passt |
|---|---|---|
| In-Memory-JS-Variable | Hoch während der Laufzeit, verloren beim Kill | OK für Access-Token nur während der Session |
localStorage / Einstellungen |
Niedrig | Nur Settings, keine Secrets |
| iOS Keychain / Android Keystore | Hoch | Über native code oder Capgo-Plugins |
| DataStore + Tink (Android nativ) | Hoch | Custom Capacitor-Plugin oder native Auth-SDK |
| Eigene Krypto in JS | Variabel | Vermeiden; Keys in JS sind extrahierbar |
Wichtig zur letzten Zeile: Beim JavaScript-Crypto liegt das Problem nicht in schwachen Algorithmen, sondern im 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 nutzt.
Migrations-Checkliste von EncryptedSharedPreferences
- Inventarisiere, welche Keys Secrets und welche öffentliche Config enthalten.
- Implementiere DataStore +
AeadSerializermit einem Keystore-basierten Tink-Keyset. - Migriere mit
SharedPreferencesMigrationüber einen Producer, der die alte EncryptedSharedPreferences Instanz öffnet (nicht im KlartextSharedPreferences), dann lösche die Legacy-Dateien nach erfolgreichem Lesen. - Roll die native Änderung mit einem Store-Release aus; teste Backup und Restore auf einem physischen Gerät.
- Entfernen
androidx.security.cryptoAbhängigkeiten, wenn nichts darauf verweist.
FAQ
Wie implementiert man sicheres Token-Speichern?
Auf iOS schreibst du Refresh-Tokens in den Keychain mit einer Zugänglichkeit, die zu den Anforderungen für Hintergrund-Refresh passt. Auf Android nutzt du einen Tink-Keyset im Android Keystore mit Jetpack DataStore und AeadSerializer statt der veralteten EncryptedSharedPreferences. Access-Tokens nur im Speicher halten und HTTPS verwenden. Füge hinzu Certificate Pinning wenn es Ihr Threat Model erfordert.
Wie verwalte und rotiere ich Refresh Tokens in einer Mobile App sicher?
Speichern Sie das Refresh Token nur in Keychain- oder Keystore-basierter Speicherung. Stellen Sie Einmal-Refresh-Tokens aus, sodass jede Einlösung ein neues Refresh Token zurückgibt und das alte ungültig macht. Halten Sie ein kurzes Reuse-Detection-Fenster aufrecht, damit ein wiederverwendetes Token die gesamte Token-Familie widerruft, mit einer kleinen Gnadenfrist für Offline- oder konkurrierende Clients.
Ist die sichere Speicherung von Access Tokens und Refresh Tokens auf iOS und Android gleich?
Die Aufteilung ist auf beiden Plattformen gleich: Speicher für kurzlebige Access Tokens, plattformgeschützter verschlüsselter Speicher für Refresh Tokens. Die APIs unterscheiden sich (Keychain versus Keystore plus DataStore), aber beide müssen WebView localStorage, UserDefaults und einfache SharedPreferences für Secrets vermeiden.
Sollte ich Token vor dem Speichern im Keychain verschlüsseln?
Für Standard-Bearer-Tokens ist normalerweise keine zusätzliche Schicht erforderlich, wenn die Keychain-Zugänglichkeit Ihrem Threat Model entspricht. Fügen Sie Envelope-Verschlüsselung hinzu, wenn Vorschriften oder Key-Rotation-Richtlinien dies erfordern.
Ersetzt SQLCipher in einem Capacitor-Plugin den Keystore?
SQLCipher schützt die Datenbankdatei, aber Sie benötigen weiterhin einen starken Schlüssel im Keystore oder Keychain, um die Datenbank zu entschlüsseln. Behandeln Sie die Verwaltung des Verschlüsselungspassworts als Teil Ihres sicheren Speicherdesigns.
Kann ich Token mit Obfuskation in Capacitor Preferences speichern?
Obskurierung ist keine Verschlüsselung. Preferences werden auf UserDefaults und SharedPreferences gemappt, ohne hardwarebasierten Schutz.
Sollten mobile Apps Refresh-Tokens speichern?
Ja, aber nur in verschlüsselter Speicherung, die auf Keychain oder Keystore basiert, kombiniert mit serverseitiger Rotation und Widerruf. Wenn man sie nicht speichert, ist bei jedem Start eine erneute Authentifizierung nötig – was Nutzer umgehen, indem sie schwächere Login-Anforderungen fordern. Unzulässig ist es unter keinen Umständen, das Refresh-Token in Preferences, localStorage oder einer Klartextdatei zu speichern.
Was ist der sicherste Weg, Access-Tokens in einer modernen App zu speichern?
Speichere sie gar nicht persistent. Halte das kurzlebige Access-Token während der Sitzung im Speicher und baue es bei jedem Start aus dem Refresh-Token neu auf. Persistiere nur das Refresh-Token in verschlüsselter Speicherung, die auf iOS Keychain oder Android Keystore basiert, und nutze Rotation plus Widerruf, um den Schaden zu begrenzen, falls ein Gerät kompromittiert wird.
Weiterlesen unter Sichere Token-Speicherung: Best Practices für Mobile Developer
Wenn du es nutzt Sichere Token-Speicherung: Best Practices für Mobile-Entwickler um Sicherheit und Compliance zu planen, verbinden Sie es mit Verschlüsselung für die Implementierungsdetails in Encryption, Konformität für das Implementierungsdetail 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.