Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Password Manager App in React Native

Password Manager App

~12 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

A password manager demonstrates ENCRYPTED storage (important, see the "AsyncStorage" chapter, where it's explicitly mentioned that AsyncStorage is NOT suitable for sensitive data), a password generator function, and a show/hide toggle.

1. Description

expo-secure-store uses the native Keychain (iOS) or Keystore (Android) – encrypted, secure storage locations, completely separate from regular (unencrypted) AsyncStorage. The API is deliberately kept almost identical to AsyncStorage (setItemAsync/getItemAsync instead of setItem/getItem).

2. Short example

import * as SecureStore from 'expo-secure-store';

await SecureStore.setItemAsync('master_password', 'secret123');
const value = await SecureStore.getItemAsync('master_password');

3. Complete project: a password vault

npx create-expo-app password-manager-app
cd password-manager-app
npx expo install expo-secure-store
utils/passwordGenerator.js
const CHARSET =
  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';

export function generatePassword(length = 16) {
  let result = '';
  for (let i = 0; i < length; i++) {
    const randomIndex = Math.floor(Math.random() * CHARSET.length);
    result += CHARSET[randomIndex];
  }
  return result;
}
App.js
import { useState, useEffect } from 'react';
import {
  View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet,
} from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { generatePassword } from './utils/passwordGenerator';

const STORAGE_KEY = 'saved_entries';

export default function App() {
  const [entries, setEntries] = useState([]);
  const [site, setSite] = useState('');
  const [password, setPassword] = useState('');
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    SecureStore.getItemAsync(STORAGE_KEY).then((stored) => {
      if (stored) setEntries(JSON.parse(stored));
    });
  }, []);

  async function persist(newEntries) {
    setEntries(newEntries);
    await SecureStore.setItemAsync(STORAGE_KEY, JSON.stringify(newEntries));
  }

  function addEntry() {
    if (!site.trim() || !password.trim()) return;
    const newEntry = { id: Date.now(), site: site.trim(), password };
    persist([newEntry, ...entries]);
    setSite('');
    setPassword('');
  }

  function deleteEntry(id) {
    persist(entries.filter((e) => e.id !== id));
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Password Vault</Text>

      <TextInput
        style={styles.input}
        placeholder="Website / Service"
        value={site}
        onChangeText={setSite}
      />

      <View style={styles.passwordRow}>
        <TextInput
          style={[styles.input, styles.passwordInput]}
          placeholder="Password"
          value={password}
          onChangeText={setPassword}
          secureTextEntry={!visible}
        />
        <TouchableOpacity onPress={() => setVisible((v) => !v)} style={styles.eyeButton}>
          <Text>{visible ? '????' : '????'}</Text>
        </TouchableOpacity>
      </View>

      <TouchableOpacity
        style={styles.generateButton}
        onPress={() => setPassword(generatePassword(16))}
      >
        <Text style={styles.generateText}>Generate secure password</Text>
      </TouchableOpacity>

      <TouchableOpacity style={styles.saveButton} onPress={addEntry}>
        <Text style={styles.saveText}>Save entry</Text>
      </TouchableOpacity>

      <FlatList
        style={styles.list}
        data={entries}
        keyExtractor={(item) => String(item.id)}
        ListEmptyComponent={<Text style={styles.empty}>No entries saved yet.</Text>}
        renderItem={({ item }) => (
          <View style={styles.entryRow}>
            <View>
              <Text style={styles.entrySite}>{item.site}</Text>
              <Text style={styles.entryPassword}>{'•'.repeat(item.password.length)}</Text>
            </View>
            <TouchableOpacity onPress={() => deleteEntry(item.id)}>
              <Text style={styles.delete}>✕</Text>
            </TouchableOpacity>
          </View>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, paddingTop: 60, paddingHorizontal: 20 },
  title: { fontSize: 22, fontWeight: 'bold', marginBottom: 16 },
  input: { borderWidth: 1, borderColor: '#d1d5db', borderRadius: 8, padding: 10, marginBottom: 10 },
  passwordRow: { flexDirection: 'row', alignItems: 'center' },
  passwordInput: { flex: 1 },
  eyeButton: { padding: 10 },
  generateButton: { paddingVertical: 8, marginBottom: 10 },
  generateText: { color: '#2563eb' },
  saveButton: { backgroundColor: '#2563eb', padding: 12, borderRadius: 8, alignItems: 'center', marginBottom: 20 },
  saveText: { color: 'white', fontWeight: 'bold' },
  list: { flex: 1 },
  empty: { textAlign: 'center', color: '#9ca3af', marginTop: 20 },
  entryRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#e5e7eb' },
  entrySite: { fontSize: 16, fontWeight: 'bold' },
  entryPassword: { color: '#6b7280', marginTop: 2 },
  delete: { color: '#dc2626', fontSize: 16, paddingHorizontal: 8 },
});

4. Explanation

  • SecureStore instead of AsyncStorage for the ENTIRE entry list – not just individual passwords, but the complete JSON dataset is stored encrypted, since it contains sensitive data.
  • The password is NEVER shown in plain text in the list ('•'.repeat(...)) – it's only briefly viewable in the input field via the eye button while creating an entry, a common security UX pattern.
  • generatePassword() uses Math.random() – sufficient for a demo/learning project, but for a REAL production app a cryptographically secure random generator (expo-crypto) would be preferable, since Math.random() is not considered security-grade.
  • persist() updates both state AND SecureStore in ONE function – prevents the two from ever drifting apart (state showing something different from what's actually stored).

5. Outputs

Ausgabe
Title "Password Vault", input fields for website and password (with an eye icon to show/hide), link "Generate secure password" (fills the password field with 16 random characters), a blue "Save entry" button. Below that, the list of saved entries, website in bold, password masked as dots, red "✕" to delete. After an app restart, all entries remain.