SecureStore — tokenlarni xavfsiz saqlash
Muammodan boshlaylik
Foydalanuvchi kirdi, server token qaytardi. Uni har safar qayta so'ramaslik uchun qurilmada saqlash kerak. AsyncStorage ga yozdingiz:
await AsyncStorage.setItem('token', token); // ❌
AsyncStorage — shifrlanmagan fayl. Root (Android) yoki jailbreak (iOS) qilingan qurilmada, zararli ilova yoki qurilmaning zaxira nusxasi orqali uni o'qish mumkin. Token qo'lga tushsa — hujumchi foydalanuvchi nomidan buyurtma beradi, karta ma'lumotlarini ko'radi.
Token, parol, maxfiy kalitlar uchun — operatsion tizimning xavfsiz xotirasi.
expo-secure-store
npx expo install expo-secure-store
| Platforma | Qayerda saqlanadi |
|---|---|
| iOS | Keychain — tizimning shifrlangan "kalitlar to'plami" |
| Android | Keystore bilan shifrlangan SharedPreferences |
Kalit apparat darajasida himoyalangan — boshqa ilovalar o'qiy olmaydi.
Ishlatish
API AsyncStorage ga juda o'xshaydi:
import * as SecureStore from 'expo-secure-store';
await SecureStore.setItemAsync('token', token);
const token = await SecureStore.getItemAsync('token'); // string | null
await SecureStore.deleteItemAsync('token');
Kalit nomlari — faqat harflar, raqamlar, ., -, _.
Auth qatlami
// lib/token.ts
import * as SecureStore from 'expo-secure-store';
const KIRISH = 'auth.access';
const YANGILASH = 'auth.refresh';
export const tokenlar = {
async saqla(access: string, refresh: string) {
await SecureStore.setItemAsync(KIRISH, access);
await SecureStore.setItemAsync(YANGILASH, refresh);
},
olish: () => SecureStore.getItemAsync(KIRISH),
yangilashOlish: () => SecureStore.getItemAsync(YANGILASH),
async tozala() {
await SecureStore.deleteItemAsync(KIRISH);
await SecureStore.deleteItemAsync(YANGILASH);
},
};
Access va refresh token
Ko'p API ikki token beradi:
- Access token — qisqa muddatli (15 daqiqa – 1 soat), har bir so'rovga qo'shiladi
- Refresh token — uzoq muddatli (30 kun), faqat yangi access token olish uchun
Access token muddati o'tganda (server 401 qaytaradi) — refresh bilan yangisini olib, so'rovni takrorlash:
// lib/api.ts (31-darsdagi api ning kengaytmasi)
let yangilanmoqda: Promise<string | null> | null = null;
async function accessniYangila(): Promise<string | null> {
const refresh = await tokenlar.yangilashOlish();
if (!refresh) return null;
const javob = await fetch(`${BASE_URL}/auth/yangilash`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh }),
});
if (!javob.ok) {
await tokenlar.tozala();
return null;
}
const { access, refresh: yangiRefresh } = await javob.json();
await tokenlar.saqla(access, yangiRefresh);
return access;
}
export async function api<T>(yol: string, options: RequestInit = {}, qaytaUrinish = true): Promise<T> {
const token = await tokenlar.olish();
const javob = await fetch(`${BASE_URL}${yol}`, {
...options,
headers: { 'Content-Type': 'application/json', ...(token && { Authorization: `Bearer ${token}` }), ...options.headers },
});
if (javob.status === 401 && qaytaUrinish) {
// Bir vaqtda 5 ta so'rov 401 olsa — faqat BITTA yangilash
yangilanmoqda ??= accessniYangila().finally(() => { yangilanmoqda = null; });
const yangi = await yangilanmoqda;
if (yangi) return api<T>(yol, options, false);
chiqishniBildir(); // auth context ga: foydalanuvchi qaytadan kirishi kerak
throw new ApiXato(401, 'Sessiya tugadi');
}
// ... qolgan qism 31-darsdagidek
}
yangilanmoqda — umumiy Promise: ekranda 5 ta so'rov bir vaqtda 401 olsa, refresh so'rovi bir marta yuboriladi va hammasi uni kutadi. Busiz 5 ta parallel refresh — ba'zi serverlar ikkinchisidan boshlab ularni rad etadi va foydalanuvchi chiqarib yuboriladi.
Biometrik himoya
Bank ilovalari kabi — tokenni olish uchun barmoq izi yoki Face ID:
await SecureStore.setItemAsync('pin_token', token, {
requireAuthentication: true,
authenticationPrompt: 'Ilovaga kirish uchun tasdiqlang',
});
const token = await SecureStore.getItemAsync('pin_token', {
requireAuthentication: true,
authenticationPrompt: 'Ilovaga kirish uchun tasdiqlang',
});
O'qishda tizim biometrik oynani ko'rsatadi. Alohida tekshirish uchun — expo-local-authentication:
import * as LocalAuthentication from 'expo-local-authentication';
const bor = await LocalAuthentication.hasHardwareAsync() && await LocalAuthentication.isEnrolledAsync();
if (bor) {
const natija = await LocalAuthentication.authenticateAsync({ promptMessage: 'Kirish' });
if (natija.success) { /* ... */ }
}
iOS da Face ID uchun app.json da tavsif kerak (NSFaceIDUsageDescription, expo-local-authentication plagini orqali).
Cheklovlar
- Hajm: bitta qiymat ~2 KB gacha (iOS da ko'proq, lekin bunga tayanmang). Faqat kichik maxfiy qatorlar
- Sekinroq — har bir o'qish shifrni ochadi. Har render da o'qimang — bir marta o'qib, xotirada saqlang
- Ilova o'chirilganda: Android da tozalanadi; iOS da Keychain saqlanib qolishi mumkin. Qayta o'rnatilgan ilova eski tokenni topadi. Kerak bo'lsa — birinchi ishga tushishda (AsyncStorage dagi bayroq bilan) tozalang
- Veb — qo'llab-quvvatlanmaydi
Nimani saqlash, nimani yo'q
| Ma'lumot | Qayerda |
|---|---|
| Access / refresh token | ✅ SecureStore |
| API kalit (foydalanuvchiga tegishli) | ✅ SecureStore |
| Parol | ❌ Umuman saqlamang — token yetarli |
| Karta raqami, CVV | ❌ Umuman saqlamang — to'lov provayderi tokenini ishlating |
| Ilova uchun umumiy "maxfiy" API kalit | ❌ Ilova ichida maxfiy kalit bo'lmaydi — server orqali |
| Mavzu, til, savat | AsyncStorage |
[!WARNING] Ilova kodiga (yoki
EXPO_PUBLIC_*ga) yozilgan har qanday kalit maxfiy emas — ilovani ochib, uni o'qish mumkin. SecureStore — foydalanuvchining maxfiy ma'lumoti uchun, sizning server kalitlaringiz uchun emas. Ular faqat serverda turadi.
Nima uchun shunday?
Nima uchun ilova o'zi shifrlab, AsyncStorage ga yozsa bo'lmaydi?
Chunki shifrlash kaliti qayerda saqlanadi? Ilova kodida — demak, uni ochgan har kim kalitni ham topadi. AsyncStorage da — xuddi shu muammo. Keychain va Keystore esa kalitni apparat darajasida (Secure Enclave, TEE) saqlaydi: uni ilova ham, operatsion tizim ham to'g'ridan-to'g'ri o'qiy olmaydi, faqat "shu ma'lumotni shifrlab/ochib ber" deb so'raydi. Bu — o'zingiz yasay olmaydigan himoya darajasi.
Xulosa
Eslab qoling:
- Token va maxfiy ma'lumot —
expo-secure-store, AsyncStorage emas - iOS — Keychain, Android — Keystore; apparat darajasida himoya
setItemAsync / getItemAsync / deleteItemAsync; qiymat ~2 KB gacha- Access + refresh:
401da bitta umumiy yangilash, so'rovni takrorlash requireAuthentication— biometrik himoya- iOS da Keychain ilova o'chirilganda qolishi mumkin
- Parol va karta ma'lumotlarini umuman saqlamang; server kalitlari — faqat serverda
Keyingi dars
36-dars: Offline rejim — tarmoq holati, kesh bilan ishlash