Hai integrato Firebase Storage nel tuo progetto. Upload di immagini funziona. Poi scopri che qualcuno ha caricato un file da 2 GB nel bucket pubblico. O che le regole di sicurezza permettono a chiunque di leggere ogni foto utente. Succede più spesso di quanto credi.
Noi, di Meteora Web, abbiamo visto progetti dove le regole di Firebase Storage erano ancora quelle di default — "allow all" — perché "tanto il frontend è protetto". Il frontend non è mai protetto abbastanza. Un bucket pubblico senza controllo è un conto che prima o poi arriva. In questa guida operativa vediamo come configurare upload di immagini e sicurezza su Firebase Storage in modo solido, concreto e senza sbavature.
Perché la sicurezza di Firebase Storage è un problema concreto?
Firebase Storage è un servizio cloud basato su Google Cloud Storage. Ogni file caricato ha un URL unico. Se le regole non sono configurate correttamente, chiunque conosca l'URL può scaricarlo. Peggio: senza limiti di upload, un utente malintenzionato può riempire il bucket di file, facendo lievitare i costi a tuo carico.
Errori comuni che vediamo:
- Regole di sicurezza aperte a tutti (lettura/scrittura per utenti anonimi)
- Nessuna validazione del tipo file o della dimensione a livello di regole
- Upload effettuato direttamente dal frontend senza token di autenticazione
- File mai cancellati dopo la rimozione dell'account utente
Non stiamo parlando di scenari astratti. Un cliente e-commerce aveva immagini prodotto caricate con regole pubbliche in lettura. Un competitor ha scaricato l'intero catalogo immagini in 10 minuti. Il costo? Zero per lui, migliaia di euro di banda per il cliente. Noi di Meteora Web ragioniamo sempre in termini di costi e rischi: una regola di sicurezza sbagliata è un costo nascosto.
Sponsored Protocol
Come configurare le regole di sicurezza per Firebase Storage?
Le regole di sicurezza si scrivono nella console Firebase, nel pannello Storage -> Regole. Sono in un linguaggio dichiarativo ispirato a Firebase Realtime Database. Ecco i tre scenari fondamentali.
1. Accesso pubblico in lettura, scrittura solo autenticata
Utile per immagini di profilo, anteprime pubbliche. Chiunque può leggere, ma solo utenti loggati possono caricare.
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
2. Accesso riservato per cartella utente
Ogni utente può leggere/scrivere solo nella propria cartella. È il pattern per profili privati o documenti personali.
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{userId}/{allPaths=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
3. Validazione del tipo file e dimensione
Per evitare caricamenti di executable o file enormi, aggiungi condizioni su request.resource.
Sponsored Protocol
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /images/{imageId} {
allow write: if request.auth != null
&& request.resource.size < 5 * 1024 * 1024 // 5 MB
&& request.resource.contentType.matches('image/.*');
allow read: if true; // se pubbliche
}
}
}
Attenzione: request.resource esiste solo durante l'operazione di scrittura. Per la lettura non serve. La validazione lato regole è il primo baluardo: fallisce anche se il client è compromesso.
Come implementare l'upload di immagini da web o mobile?
Usiamo l'SDK Firebase v9 modulare (compat, ma consigliamo modular). Ecco uno snippet per upload con feedback di progresso e metadati.
Upload con metadati e progresso
import { getStorage, ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
import { getAuth } from 'firebase/auth';
const storage = getStorage();
const auth = getAuth();
function uploadImage(file) {
const user = auth.currentUser;
if (!user) throw new Error('Utente non autenticato');
// Percorso sicuro: /users/{uid}/images/{timestamp}_{nomefile}
const storageRef = ref(storage, `users/${user.uid}/images/${Date.now()}_${file.name}`);
const metadata = {
contentType: file.type,
customMetadata: {
'uploadedBy': user.uid,
'originalName': file.name
}
};
const uploadTask = uploadBytesResumable(storageRef, file, metadata);
uploadTask.on('state_changed',
(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload progress:', progress);
},
(error) => {
console.error('Upload error:', error);
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
console.log('File disponibile a:', downloadURL);
// Salva URL nel database Firestore o altrove
});
}
);
}
Perché fare così? Il percorso include l'UID utente, rispettando le regole di sicurezza della sezione 2. I metadati personalizzati servono per audit. Il progresso evita UX frustrante. E gestiamo l'errore: se l'utente perde autenticazione, Firebase restituisce un errore 403.
Sponsored Protocol
Upload da mobile (Flutter esempio)
import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:io';
Future uploadImage(File file) async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) throw Exception('Non autenticato');
final ref = FirebaseStorage.instance
.ref('users/${user.uid}/images/${DateTime.now().millisecondsSinceEpoch}_${file.path.split('/').last}');
final snapshot = await ref.putFile(file);
return await snapshot.ref.getDownloadURL();
}
Quali accorgimenti adottare per ottimizzare storage e costi?
L'upload è solo l'inizio. Se non controlli dimensione e qualità, il bucket cresce e la bolletta sale. Ecco le nostre best practice.
Sponsored Protocol
Compressione e ridimensionamento lato client
Prima di caricare, riduci l'immagine sul dispositivo. Su web, usa canvas per ridimensionare. Su mobile, librerie come image_picker con opzione maxWidth. Noi abbiamo ridotto il peso medio delle immagini di un cliente del 60% senza perdita visibile di qualità, con un semplice resize a 1200px di lato maggiore.
Generazione di thumbnail con Cloud Functions
Scatta una Cloud Function che si attiva al caricamento (onFinalize) e genera una versione piccola. Esempio con Sharp su Node.js:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const sharp = require('sharp');
admin.initializeApp();
exports.generateThumbnail = functions.storage.object().onFinalize(async (object) => {
const bucket = admin.storage().bucket(object.bucket);
const filePath = object.name;
const fileName = filePath.split('/').pop();
const thumbPrefix = 'thumb_';
if (fileName.startsWith(thumbPrefix)) return null;
const thumbPath = filePath.replace(fileName, `${thumbPrefix}${fileName}`);
const downloadedFile = bucket.file(filePath);
const tempLocalPath = `/tmp/${fileName}`;
const thumbLocalPath = `/tmp/${thumbPrefix}${fileName}`;
await downloadedFile.download({destination: tempLocalPath});
await sharp(tempLocalPath).resize(200, 200).toFile(thumbLocalPath);
await bucket.upload(thumbLocalPath, {destination: thumbPath});
});
Policy di retention e pulizia automatica
Imposta una regola GCS Lifecycle Management per eliminare file non letti dopo 90 giorni. Oppure, alla cancellazione di un utente, esegui una Cloud Function che rimuove la sua cartella Storage. Noi lo facciamo sempre: un cliente dimentica di pulire e i costi di storage si accumulano silenziosamente.
Sponsored Protocol
Cosa fare adesso
Ecco tre azioni concrete da eseguire oggi sul tuo progetto Firebase:
- Controlla le regole attuali nella console Firebase > Storage > Regole. Se sei in produzione e hai
allow read, write: if true;— correggi immediatamente. Usa almeno il pattern per utenti autenticati. - Aggiungi validazione di tipo e dimensione nelle regole. Non fidarti mai del solo frontend. Un test rapido: prova a caricare un file .exe — se passa, le regole sono bucate.
- Implementa l'upload con percorso basato su UID e progresso. Se usi già l'SDK, verifica che il percorso corrisponda esattamente alla regola
match /users/{userId}/....
Per approfondire l'ecosistema Firebase completo, leggi la nostra guida pillar su Firebase per app web e mobile.