Recently, while I was building an application on Firebase and implementing Firebase Storage, I realised that I have to always consult old Firebase projects I have built or scour the web for blogs and documentation that explain not only how to do uploads to Firebase Storage but also write rules to protect them.
My use-case is as follows:
- Users can create listings
- A listing can have one or more images
- An image is uploaded to Firebase Storage in the form of
listing-images/userId/filename.extension
- I only want to allow users to write to this folder if they are that user currently logged in. This folder becomes a bucket for all of the users listing images.
Uploading Files
const storage = firebase.storage().ref(`listing-images/${this.auth.currentUser.uid}/${image.name}`); const upload = storage.put(image); upload.on('state_changed', // Uploading... (snapshot) => { this.frontImageUploading = true; }, // Error () => { this.frontImageUploading = false; }, // Complete () => { this.frontImageUploading = false; } );
Uploading files with Firebase’ Javascript SDK could not be any easier. You create a reference to where you want to store a file and then you call the put
method providing a file to upload.
We then listen to the state_changed
event which can take three callback functions. The first is called as the file is uploaded, the second is the error callback and the third is called when a file has been uploaded.
Firebase Storage Rules
My storage.rules
file ended up looking like this. Your use-case might differ, but the premise is the same.
I want to allow all reads as these images are public, so I create a rule called allow read
and provide if true
as the value.
For image writes, I first check if the currently logged in user matches the userId
provided in the image path. I then check if the size of the image is less than 5mb and if its content type is an image.
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /listing-images/{userId}/{allPaths=**} {
allow read: if true;
allow write: if request.auth.uid == userId && request.resource.size < 5 * 1024 * 2014 && request.resource.contentType.matches('image/.*');
}
}
}
This allows users to only be able to upload to their specific folder inside of listing-images
. Anyone can read it, but only the logged-in user can upload here. It’s simple and it works.
Accessing The Uploaded File
Once the file has successfully uploaded, you most likely want to access some information about it like where it uploaded and so on. We can use the reference we created to get that information.
const storage = firebase.storage().ref(`listing-images/${this.auth.currentUser.uid}/${image.name}`); const upload = storage.put(image); upload.on('state_changed', // Uploading... (snapshot) => { this.frontImageUploading = true; }, // Error () => { this.frontImageUploading = false; }, // Complete async () => { this.frontImageUploading = false; const meta = await storage.getMetadata(); } );
We made the success callback async
and then we awaited the getMetadata
method which is called on the ref
itself. This gives us information like which bucket the file is in, the fullPath
, it’s md5Hash
, size
and other useful values.
If you want to generate an image URL string to the file which can be used in the browser, you can call getDownloadURL();
like so.
const url = await storage.getDownloadURL();