import { ExpoConfig, ConfigContext } from 'expo/config';
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: 'AppSecurizat',
slug: 'app-securizat',
extra: {
// Variabila e citită doar pe mașina de EAS Build
// Nu va ajunge direct în JS-ul livrat userului
easProjectId: process.env.EAS_PROJECT_ID,
},
hooks: {
postPublish: [
{
file: 'sentry-expo/upload-sourcemaps',
config: {
// Token-ul e folosit doar la build pentru uploade sourcemaps
authToken: process.env.SENTRY_AUTH_TOKEN,
},
},
],
},
});Salutare băieți! Am văzut săptămâna trecută încă un proiect de React Native unde cheia secretă de la Supabase Service Role era împachetată frumos în bundle-ul de JS. Un simplu jadx-gui pe APK-ul de Android și în 30 de secunde aveam acces full la baza lor de date.
Trebuie să lămurim o chestie o dată pentru totdeauna: tot ce ajunge în codul de client (React Native / Expo) este PUBLIC, indiferent dacă îl pui în .env.production sau .env.development.
Marea confuzie: EXPO_PUBLIC_ nu ascunde nimic
De la Expo SDK 49 încoace, avem suport nativ pentru variabile de mediu prefixate cu EXPO_PUBLIC_. Este un feature super comod pentru că nu mai avem nevoie de react-native-dotenv sau hacks în babel.config.js.
Dar am întâlnit mulți juniori și mid care cred că dacă pun o cheie în .env.production și nu o commitează pe Git, ea devine magic securizată. Nu, tată. La build time, Metro bundler înlocuiește process.env.EXPO_PUBLIC_API_URL cu string-ul hardcodat. În fișierul .js final generat, valoarea e text chior.
Dacă cineva își descarcă APK-ul tău sau extrage bundle-ul dintr-un .ipa (se face extrem de ușor), dă un simplu grep după URL-uri sau prefixe de API și ți-a luat cheia.
Unde intră în scenă EAS Secrets?
Aici intervine Expo Application Services (EAS). Mulți încurcă EAS Secrets cu un seif magic care protejează aplicația în runtime.
EAS Secrets sunt variabile de mediu puse pe serverele Expo. Ele sunt injectate DOAR în timpul build-ului pe mașinile lor de CI/CD (EAS Build).
Sunt perfecte pentru:
- Keystore passwords și aliniamente de Android
- Provisioning profiles de iOS
- Chei de la servicii terțe folosite doar în
app.config.ts(de exemplu, un token Sentry necesar când urci sourcemaps)
Dar atenție la trade-off! Dacă folosești un EAS Secret ca să populezi o variabilă pe care o citești mai apoi în componentele de React (const key = process.env.MY_EAS_SECRET), valoarea aia tot în bundle-ul public ajunge. EAS Secrets protejează secretul pe GitHub și în pipeline-ul de CI, dar NU îl ascunde de userul final dacă îl trimiți în JS.
Cum arată o arhitectură curată?
La un proiect recent de e-commerce cu 18k useri activi, am organizat lucrurile foarte strict și am scăpat de orice stres de securitate:
- În React Native/Expo app: Ținem DOAR chei publice (
EXPO_PUBLIC_SUPABASE_ANON_KEY,EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY, API URL-ul). Nu există nicio excepție. - În EAS Secrets: Ținem token-urile de build-time (
SENTRY_AUTH_TOKEN,MATCH_PASSWORD) și le citim exclusiv înapp.config.ts. - Pentru operațiuni sensibile: Facem un backend proxy (Serverless Function în Vercel/Supabase Edge Functions). Dacă aplicația trebuie să proceseze o plată sau să apeleze un LLM, apelează backend-ul propriu, iar backend-ul folosește cheia secretă stocată în mediul lui izolat.
Trade-off-ul e evident: adaugi puțină latență (un hop în plus către un Edge Function) și ai puțin mai mult de scris. Însă alternativa e să te trezești cu contul de OpenAI sau Stripe golit într-un weekend.
Voi cum gestionați cheile de API în aplicațiile de Expo? Mai folosiți module de SecureStore pentru tokens de sesiune sau le băgați pe toate direct în Zustand?