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-storeconst 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;
}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
SecureStoreinstead ofAsyncStoragefor 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()usesMath.random()– sufficient for a demo/learning project, but for a REAL production app a cryptographically secure random generator (expo-crypto) would be preferable, sinceMath.random()is not considered security-grade.persist()updates both state ANDSecureStorein ONE function – prevents the two from ever drifting apart (state showing something different from what's actually stored).