React Native Sicherer Speicher: Secure Storage und Keychain
AI generated
RN
native
React Native · iOS · Android · Security
React Native Sicherer Speicher: Secure Storage und Keychain
warum Tokens niemals in AsyncStorage gehören

AsyncStorage speichert Werte unverschlüsselt als Klartext, auslesbar bei physischem Zugriff oder auf gerooteten Geräten. Secure Storage über die iOS Keychain und den Android Keystore verschlüsselt Secrets auf Betriebssystemebene und kann sie zusätzlich an Biometrie binden. Dieser Artikel zeigt, wie react-native-keychain und expo-secure-store Access-Tokens, Refresh-Tokens und andere sensible Daten korrekt ablegen.

17 Min. Lesezeit Keychain · Android Keystore · Secure Storage iOS · Android · Expo

1. Warum AsyncStorage für Secrets ungeeignet ist

AsyncStorage speichert Werte unter iOS als Property-List-Dateien und unter Android als SQLite-Datenbank, in beiden Fällen unverschlüsselt im Klartext. Wer physischen Zugriff auf ein Gerät hat oder ein gerootetes beziehungsweise jailbroken Gerät untersucht, kann diese Dateien direkt öffnen und lesen, ganz ohne den App-Code zu analysieren. Für nicht-sensible Einstellungen wie ein Theme-Flag ist das unproblematisch, für Access-Tokens, Refresh-Tokens oder API-Schlüssel jedoch ein direktes Sicherheitsrisiko.

Der Denkfehler, der in der Praxis oft zu genau diesem Problem führt: AsyncStorage fühlt sich aus der API her wie ein einfacher Key-Value-Store an, ohne dass die fehlende Verschlüsselung im Entwickler-Alltag sichtbar wird. Ein Penetrationstest oder eine Backup-Analyse deckt das Problem dann meist erst spät im Projekt auf. Secure Storage muss deshalb von Anfang an eingeplant werden, sobald irgendein Secret dauerhaft auf dem Gerät liegen soll.

Die richtige Grundregel für jedes React-Native-Projekt: Alles, was ein Angreifer mit Gerätezugriff nicht im Klartext sehen soll, gehört in einen Speicher, der vom Betriebssystem selbst verschlüsselt wird, also in die Keychain unter iOS beziehungsweise den Android Keystore. AsyncStorage bleibt für unkritische App-Einstellungen weiterhin die richtige Wahl, aber niemals für Tokens oder Zugangsdaten.

2. Überblick sicherer Speicherlösungen

Für Secure Storage in React Native haben sich mehrere Bibliotheken etabliert. react-native-keychain ist die verbreitetste Wahl im Bare Workflow und bietet direkten Zugriff auf iOS Keychain und Android Keystore inklusive Biometrie-Bindung. expo-secure-store ist das Pendant für den Expo-Managed-Workflow mit einer bewusst schlankeren API. Verschlüsseltes MMKV bietet zusätzlich sehr hohe Performance für häufige Lese-/Schreibzugriffe, während react-native-encrypted-storage eine einfache, AsyncStorage-ähnliche API mit Verschlüsselung darunter kombiniert.

Die Wahl hängt vom Workflow und den Performance-Anforderungen ab: Wer ohnehin im Expo-Managed-Workflow arbeitet und keine native Konfiguration vornehmen möchte, ist mit expo-secure-store gut bedient. Wer feingranulare Kontrolle über Access-Control-Flags und Biometrie-Bindung benötigt, etwa für Banking- oder Health-Apps, kommt an react-native-keychain kaum vorbei, da es die native Keychain-API direkter durchreicht.

Wichtig ist in jedem Fall: Alle diese Lösungen sind letztlich dünne Wrapper um dieselben zwei Betriebssystem-Mechanismen, iOS Keychain Services und Android Keystore. Das Verständnis dieser beiden Grundlagen, das in den nächsten zwei Abschnitten folgt, ist deshalb wichtiger als die Detailkenntnis einer einzelnen Bibliotheks-API.

Verschlüsseltes MMKV verdient dabei eine gesonderte Erwähnung: Es verschlüsselt Werte selbst mit einem symmetrischen Schlüssel, bevor sie auf die Festplatte geschrieben werden, während dieser Schlüssel selbst wiederum in echtem Secure Storage liegen sollte. Diese Kombination aus schneller, verschlüsselter Massenspeicherung und einem kleinen, hardwaregeschützten Master-Schlüssel ist ein bewährtes Muster für Apps, die viele verschlüsselte Datensätze mit geringer Latenz lesen und schreiben müssen.

3. iOS Keychain Grundlagen

Die Keychain ist eine vom Betriebssystem verwaltete, verschlüsselte Datenbank, komplett getrennt vom übrigen App-Sandbox-Dateisystem. Werte, die in der Keychain landen, werden mit einem Hardware-gestützten Schlüssel verschlüsselt, der niemals die Secure Enclave verlässt. Selbst ein vollständiges Dateisystem-Backup enthält die Keychain-Inhalte nur in verschlüsselter Form, sodass ein reines Kopieren des Backups die Secrets nicht preisgibt.

Über Access-Control-Flags wird zusätzlich gesteuert, wann und unter welchen Umständen ein Eintrag lesbar ist. kSecAttrAccessibleWhenUnlockedThisDeviceOnly ist für die meisten Anwendungsfälle die richtige Wahl: Der Eintrag ist nur bei entsperrtem Gerät lesbar und wird zusätzlich explizit vom iCloud-Keychain-Backup ausgeschlossen, verlässt das Gerät also nie, selbst nicht in verschlüsselter Form.

Für Einträge, die zusätzlich an Biometrie gebunden werden sollen, kommt SecAccessControlCreateFlags mit der Flag biometryCurrentSet zum Einsatz. Das bindet den Eintrag an die aktuell auf dem Gerät hinterlegte Biometrie, sodass ein Zurücksetzen oder Neu-Einrichten von Face ID beziehungsweise Touch ID den Zugriff auf den Eintrag automatisch invalidiert, eine wichtige Sicherheitseigenschaft gegen nachträglich hinzugefügte fremde biometrische Daten.

4. Android Keystore Grundlagen

Das Android Keystore System ist das funktionale Gegenstück zur iOS Keychain: kryptografische Schlüssel werden hardwaregestützt erzeugt und verwaltet, ohne dass der private Schlüssel selbst jemals als Rohdaten für die App zugänglich ist. Statt den Schlüssel direkt auszulesen, nutzt die App ihn nur indirekt über Verschlüsselungs- und Entschlüsselungs-Operationen, die vom Keystore ausgeführt werden.

EncryptedSharedPreferences aus der Jetpack Security Library baut als High-Level-API direkt auf dem Keystore auf und bietet ein API, das sich fast identisch zu normalen SharedPreferences anfühlt, jedoch Schlüssel und Werte transparent verschlüsselt. Die meisten React-Native-Bibliotheken für Secure Storage nutzen unter der Haube genau diesen Mechanismus, um auf Android eine vergleichbare Sicherheit wie die iOS Keychain zu erreichen.

Auf Geräten mit entsprechender Hardware steht zusätzlich StrongBox zur Verfügung, ein dediziertes Sicherheitschip-Modul, das Schlüsseloperationen komplett isoliert vom Hauptprozessor ausführt. StrongBox bietet damit eine noch stärkere Isolation als der Software-Keystore allein, ist aber nicht auf jedem Android-Gerät verfügbar und sollte als optionale Verbesserung, nicht als Voraussetzung behandelt werden.


// AuthStorage.js — react-native-keychain with biometry-gated access
import * as Keychain from 'react-native-keychain';

async function saveRefreshToken(token) {
  await Keychain.setGenericPassword('refresh_token', token, {
    service: 'de.mironsoft.app.refreshToken',
    accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE,
    accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
  });
}

async function loadRefreshToken() {
  const credentials = await Keychain.getGenericPassword({
    service: 'de.mironsoft.app.refreshToken',
  });
  return credentials ? credentials.password : null;
}

async function clearRefreshToken() {
  await Keychain.resetGenericPassword({
    service: 'de.mironsoft.app.refreshToken',
  });
}

export { saveRefreshToken, loadRefreshToken, clearRefreshToken };

5. Implementierung mit react-native-keychain

Die Grundfunktionen setGenericPassword() und getGenericPassword() speichern beziehungsweise laden einen Wert unter einem Benutzernamen und einem Passwort-Feld, wobei in der Praxis oft nur das Passwort-Feld für den eigentlichen Token verwendet wird. Über die accessControl-Option lässt sich, wie oben gezeigt, ein Eintrag zusätzlich an Biometrie oder den Geräte-Passcode binden, sodass ein reines Auslesen der Keychain-Datenbank ohne erfolgreiche Authentifizierung nicht ausreicht.

Das service-Namensfeld dient als Namespace, um mehrere unabhängige Credentials im selben Projekt getrennt zu speichern, etwa einen Access-Token und einen Refresh-Token unter unterschiedlichen Service-Namen. Ohne diese Trennung würde ein zweiter setGenericPassword()-Aufruf den ersten Eintrag überschreiben, da react-native-keychain standardmäßig nur einen einzigen generischen Eintrag pro Service verwaltet.

In der Praxis bewährt sich, den Zugriff auf besonders sensible Einträge, etwa den Refresh-Token, testweise auch im Fehlerfall zu behandeln: Schlägt die Biometrie-Prüfung fehl oder wird sie vom Nutzer abgebrochen, wirft getGenericPassword() einen Fehler, den die App sauber abfangen und dem Nutzer verständlich kommunizieren sollte, statt die Anwendung abstürzen zu lassen.

6. Implementierung mit expo-secure-store

Im Expo-Managed-Workflow übernimmt expo-secure-store dieselbe Aufgabe mit einer bewusst reduzierten API: setItemAsync(key, value) und getItemAsync(key) speichern und lesen Werte unter einem einfachen Schlüssel, ohne die native Konfiguration zu berühren. Intern nutzt die Bibliothek unter iOS ebenfalls die Keychain und unter Android den Keystore, sodass die Sicherheitsgarantien identisch zu react-native-keychain sind.

Über die keychainAccessible-Option lässt sich unter iOS das gleiche Access-Control-Verhalten wie bei react-native-keychain konfigurieren. Eine praktische Einschränkung betrifft Android: Aufgrund der zugrunde liegenden Keystore-Mechanik ist die Größe eines gespeicherten Werts auf ungefähr 2 KB begrenzt, was für Tokens in der Regel ausreicht, für größere Datensätze wie ganze Objekte oder Bilder jedoch nicht geeignet ist.

Wer größere, aber dennoch sensible Datenmengen ablegen möchte, sollte deshalb einen hybriden Ansatz verfolgen: Den eigentlichen Verschlüsselungsschlüssel klein und in Secure Storage ablegen, die eigentlichen, damit verschlüsselten Daten hingegen im normalen Dateisystem oder in einer verschlüsselten Datenbank wie MMKV speichern. Dieses Muster umgeht die Größenbeschränkung, ohne die Sicherheit zu kompromittieren.


// SecureStoreExample.js — expo-secure-store usage
import * as SecureStore from 'expo-secure-store';

async function saveAccessToken(token) {
  await SecureStore.setItemAsync('access_token', token, {
    keychainAccessible: SecureStore.WHEN_UNLOCKED,
  });
}

async function loadAccessToken() {
  try {
    return await SecureStore.getItemAsync('access_token');
  } catch (error) {
    // Item not found or keychain inaccessible — treat as logged out
    return null;
  }
}

async function clearAccessToken() {
  await SecureStore.deleteItemAsync('access_token');
}

export { saveAccessToken, loadAccessToken, clearAccessToken };

7. Token-Storage-Strategie für Authentifizierung

Für Authentifizierungs-Flows lohnt sich eine klare Trennung zwischen Access-Token und Refresh-Token. Der Access-Token ist kurzlebig, wird bei jeder API-Anfrage mitgeschickt und kann bei Kompromittierung nur für ein kurzes Zeitfenster missbraucht werden. Der Refresh-Token ist langlebiger und deutlich schützenswerter, da er das Erneuern des Access-Tokens ohne erneute Login-Eingabe erlaubt. Beide gehören in Secure Storage, aber der Refresh-Token verdient die strengere Access-Control-Konfiguration.

Token-Rotation, bei der jeder Refresh-Aufruf einen neuen Refresh-Token ausstellt und den alten invalidiert, begrenzt zusätzlich das Risiko eines gestohlenen, aber noch nicht verwendeten Tokens. Wichtig dabei: Das komplette JWT-Signing-Secret gehört niemals clientseitig gespeichert, da die App damit selbst gültige Tokens fälschen könnte. Die Signatur-Prüfung bleibt ausschließlich Aufgabe des Backends.

Ein biometrie-gated Refresh-Token-Abruf fügt eine zusätzliche Sicherheitsschicht hinzu: Statt den Refresh-Token bei jedem App-Start automatisch und ohne Nutzerinteraktion auszulesen, verlangt die App zusätzlich eine Biometrie-Bestätigung, bevor der Token aus der Keychain gelesen wird. Das verhindert, dass ein entsperrtes, aber unbeaufsichtigtes Gerät automatisch eine bestehende Session fortsetzt.

8. Migration und Fehlerbehandlung

Bestehende Apps, die Tokens fälschlicherweise in AsyncStorage abgelegt haben, sollten die Migration zu Secure Storage beim nächsten App-Start automatisch durchführen: Vorhandene Werte aus AsyncStorage lesen, in die Keychain beziehungsweise den Android Keystore schreiben, und die ursprünglichen AsyncStorage-Einträge anschließend löschen. Diese Migration sollte einmalig laufen und über ein Flag markiert werden, damit sie nicht bei jedem App-Start unnötig wiederholt wird.

Nach einer Geräte-Wiederherstellung aus einem Backup, etwa beim Wechsel auf ein neues Gerät, können Keychain-Zugriffe fehlschlagen, weil hardwaregebundene Schlüssel nicht über ein Backup übertragbar sind. Das betrifft besonders Einträge mit ThisDeviceOnly-Access-Control-Flags. Die App sollte diesen Fall als normale Ausnahme behandeln, den betroffenen Token stillschweigend verwerfen und den Nutzer zu einer erneuten Anmeldung auffordern, statt abzustürzen.

Generell gilt: Ein "Item not found"-Fehler beim Lesen aus Secure Storage ist ein vollkommen normaler, erwartbarer Zustand, etwa beim allerersten App-Start ohne vorherige Anmeldung. Jeder Lesezugriff sollte deshalb defensiv mit Try-Catch oder einer entsprechenden Fehlerprüfung umgeben sein und diesen Fall explizit als "nicht eingeloggt" statt als technischen Fehler interpretieren.

Auch beim Deinstallieren und erneuten Installieren einer App verhält sich Secure Storage plattformabhängig unterschiedlich: Unter Android werden Keystore-Einträge beim Deinstallieren zuverlässig entfernt, während iOS Keychain-Einträge in manchen Konfigurationen eine Neuinstallation überdauern können. Wer sich darauf verlässt, dass eine Neuinstallation automatisch alle Secrets zurücksetzt, sollte dieses Verhalten für die jeweilige Plattform gezielt testen, statt es stillschweigend vorauszusetzen.

Ein einfacher Test dafür: App installieren, einen Test-Token in Secure Storage schreiben, App deinstallieren, App neu installieren und prüfen, ob der Test-Token noch lesbar ist. Dieser manuelle Check deckt plattformspezifisches Fehlverhalten früh auf, bevor er in Produktion zu unerwartet fortbestehenden Sessions führt.

9. Speicherlösungen im Vergleich

Die folgende Übersicht fasst zusammen, welche Speicherlösung sich für welchen Anwendungsfall in React Native eignet.

Lösung Verschlüsselung Biometrie-Bindung Einsatzzweck
AsyncStorage Keine Nicht möglich Unkritische App-Einstellungen
Verschlüsseltes MMKV Ja, symmetrisch Indirekt über Schlüssel Große, performancekritische Datenmengen
react-native-keychain / expo-secure-store Ja, Keychain / Keystore Ja, nativ unterstützt Tokens, Zugangsdaten, kleine Secrets

Für die überwiegende Mehrheit der Anwendungsfälle, insbesondere Access-Tokens und Refresh-Tokens, ist die Kombination aus react-native-keychain oder expo-secure-store mit passenden Access-Control-Flags die richtige Wahl. Verschlüsseltes MMKV ergänzt diese Lösung sinnvoll, wenn größere verschlüsselte Datenmengen mit hoher Lese-/Schreibfrequenz gespeichert werden müssen, während AsyncStorage konsequent auf unkritische Einstellungen beschränkt bleiben sollte.

Mironsoft

React Native Entwicklung für iOS und Android

Secrets, die wirklich sicher auf dem Gerät liegen?

Wir migrieren bestehende AsyncStorage-Tokens auf Keychain und Android Keystore, konfigurieren Access-Control-Flags passend zu eurem Sicherheitsmodell und bauen eine belastbare Token-Storage-Strategie für euren Login-Flow.

Keychain & Keystore

react-native-keychain und expo-secure-store korrekt konfigurieren

Token-Strategie

Access-Token/Refresh-Token-Trennung, Rotation, biometrie-gated Zugriff

Migration

Sichere Übernahme bestehender AsyncStorage-Daten ohne Downtime

10. Zusammenfassung

Secure Storage in React Native bedeutet, Secrets nicht in unverschlüsseltem AsyncStorage, sondern in der vom Betriebssystem verschlüsselten Keychain unter iOS beziehungsweise im Android Keystore abzulegen. react-native-keychain und expo-secure-store reichen diese nativen Mechanismen über eine einheitliche API durch, inklusive Access-Control-Flags wie WhenUnlockedThisDeviceOnly und optionaler Biometrie-Bindung über biometryCurrentSet.

Für Authentifizierungs-Flows lohnt sich eine klare Trennung zwischen kurzlebigem Access-Token und schützenswertem Refresh-Token, kombiniert mit Token-Rotation und, wo sinnvoll, einem biometrie-gated Zugriff auf besonders sensible Einträge. Migration von bestehendem AsyncStorage-Code und robuste Fehlerbehandlung bei fehlenden Einträgen oder nach Geräte-Wiederherstellungen runden ein solides Secure-Storage-Konzept ab, ohne die Nutzererfahrung durch technische Fehler zu stören.

React Native Secure Storage und Keychain — Das Wichtigste auf einen Blick

Nie AsyncStorage für Secrets

AsyncStorage ist unverschlüsselt. Tokens und Zugangsdaten gehören immer in Keychain/Keystore.

Access-Control-Flags

WhenUnlockedThisDeviceOnly und biometryCurrentSet binden Einträge an Gerätezustand und Biometrie.

Bibliotheken

react-native-keychain (Bare Workflow) und expo-secure-store (Managed Workflow) kapseln Keychain/Keystore.

Token-Strategie

Access-Token kurzlebig, Refresh-Token strenger geschützt, Rotation und niemals das JWT-Secret clientseitig.

11. FAQ: React Native Secure Storage und Keychain

1Warum ist AsyncStorage ungeeignet für Tokens?
Unverschlüsselte Klartext-Speicherung, direkt auslesbar bei physischem Zugriff oder auf gerooteten Geräten.
2react-native-keychain oder expo-secure-store?
expo-secure-store im Managed Workflow ohne native Konfiguration, react-native-keychain im Bare Workflow für mehr Kontrolle.
3Größenlimit auf Android?
Rund 2 KB pro Wert wegen der Keystore-Mechanik. Größere Daten verschlüsselt im Dateisystem ablegen, nur den Schlüssel in Secure Storage.
4Was bedeutet WhenUnlockedThisDeviceOnly?
Nur bei entsperrtem Gerät lesbar, explizit vom iCloud-Backup ausgeschlossen. Der Wert verlässt das Gerät nie.
5Wie bindet man Einträge an Biometrie?
Über biometryCurrentSet in SecAccessControlCreateFlags. Ändert sich die hinterlegte Biometrie, wird der Zugriff automatisch invalidiert.
6Ist StrongBox zwingend nötig?
Nein, eine optionale zusätzliche Sicherheitsstufe auf unterstützten Geräten, keine Voraussetzung für sicheren Speicher.
7Wie migriere ich von AsyncStorage?
Werte einmalig auslesen, in Keychain/Keystore schreiben, alte Einträge löschen, Migration per Flag als abgeschlossen markieren.
8Warum schlägt Zugriff nach Geräte-Restore fehl?
Hardwaregebundene Schlüssel mit ThisDeviceOnly übertragen sich nicht auf ein neues Gerät. Fehler abfangen und erneute Anmeldung anfordern.
9Access-Token und Refresh-Token gleich behandeln?
Nein. Refresh-Token ist langlebiger und schützenswerter, verdient strengere Access-Control-Flags als der kurzlebige Access-Token.
10Können Backups Secrets leaken?
Bei korrekten ThisDeviceOnly-Flags nicht, da diese Einträge explizit vom Backup ausschließen. Ohne diese Flags theoretisch möglich.