How to Access and Download Files in Cloud Storage

Peter Kracik
Firebase Developers
6 min readJun 9, 2020

There are multiple ways to use images or other files in Google Cloud Storage or Firebase Storage* in your Firebase web application, and each of them has its use and of course pros and cons.

* Firebase Storage is a product derived from Google Cloud Storage. In most cases you can work with it as if it was native Google Cloud Storage. You can for example access your buckets directly via Google Cloud console, where you have also much more possibilities, but it comes also with its Firebase perks focused for mobile/web apps development. You can read more about differences here.

Let’s take a look at a couple of ways to manage access to files:

1. Publicly accessible with pretty-ish URLs

In case you want publicly accessible images or other file types, for example product images in your e-shop, and without using any kind of restrictions, you can make these files publicly available.

Bucket level

If you make the whole bucket publicly accessible, you go to Storage inside your Google Cloud console. Select the bucket you want to make accessible and in the panel on the right side, add a user allUsers with permissions Storage Object Viewer.

Select allUsers with Storage Object Viewer permissions

Now each item inside your bucket has a little warning sign with the public link.

Folder / File level

I’ve recently discovered that we can make specific files or folders publicly accessible within a bucket in case your bucket has the default fine-grained access control. However this is not possible through the interface — we need to do it inside a terminal with the command

gsutil acl ch -u AllUsers:R gs://[BUCKET_NAME]/[FOLDER_NAME]/

or a “shortcut”

gsutil acl set -r public-read gs://[BUCKET_NAME]/[FOLDER_NAME]/

Unfortunately, these security rules are applied only on objects in these folders, not on folders. And it means if we upload a new file, it won’t have a public access link unless we run the above command again.

Your files are now accessible via a quite simple URL:

https://storage.googleapis.com/[BUCKET_NAME]/[FILE_PATH]

How to retrieve a file link

There is no method in the official Firebase libraries to get this URL*. You can, however, create it manually and store it inside Firestore. So you would have direct access to all URLs you need without querying storage objects.

An example of cloud function to store the URL in Firestore.

*Object names should be encoded https://cloud.google.com/storage/docs/request-endpoints#encoding

2. Hidden but still publicly accessible

Another use case could be a photo gallery. Imagine that you want to build a website with all those awesome photos you took. You want them to be visible on the front-end, but not that easy accessible by guessing the URL.

To achieve this in our application, we can call getDownloadUrl() on a bucket object, which will return a public URL containing a unique token.

As mentioned in the beginning, Google Cloud Storage and Firebase Storage have small differences and this is one of them: getDownloadUrl() works only with Firebase Storage.

The URL will look like this:
https://firebasestorage.googleapis.com/v0/b/[BUCKET_NAME]/o/[FILE_PATH]?alt=media&token=6f08efe4–73a3–4686–80c8–966b8d5c4324

The token ensures that the URL cannot be easily guessed. And you can easily revoke the token in case the URL got into the wrong hands.

We can update token in the image’s info bar

This URL is publicly accessible, but calls to getDownloadUrl() function are subject of Storage rules. If rules don’t allow you access to this file object in Google Cloud Storage, you won’t be able to call the function to retrieve the URL. However if somebody sends you the URL directly, you can access the file.

This function can be called only on the client side, it is not possible to call it in Google Cloud Functions. So every time you need the file, you need a query for the object in Storage and then call the function.

The downside of this in my gallery example would be the amount of requests and also the function is asynchronous so there might be a possible delay. One solution would be to store this generated URL in Firestore once we retrieve it.

3. Signed URL

If you store important files that must not be accessible without the proper permissions, you can use a signed URL. Its a really good solution if you want to share documents like invoices, contracts etc…

Signed URLs can be created on the server-side and we add an expiration time to define for how long it will be valid. Here is an example of a cloud function to generate a signed URL. In this example I don’t provide any security logic, whoever can call this function would get the URL.

Cloud function

Example of call on client-side

const fn = this.functions.httpCallable('getUrl');
fn({}).subscribe(data => { console.log(data.url) }

4. Downloading files directly

Another possibility is to send the file as a stream from the server-side in a Firebase Cloud function. The storage needs to be initialized with admin permissions. If it was initialize with default Storage() it wouldn’t allow us to access private files.

Function to send file as a stream
Accessible by GET/POST request

*This function wouldn’t work inside a client-side httpsCallable, because this method accepts only JSON as a response. So we need to fetch it as a classic GET request.

As I have already mentioned, this is not secure! It is called with admin permissions, so it will have full access to your files. You need to implement your security rules inside this Cloud function.

Fine-grained access with storage rules

Rules are probably the most important feature of Firebase Storage in comparison to Google Cloud Storage. They work on the client-side and leverage Firebase Authentication as an addition to IAM security rules on the server side.

Thanks to them we can specify fine-grained access to objects in a bucket from our application based on object’s and/or user’s properties. The important thing to remember is that storage rules control permissions of access the object from the client, not permissions of the object itself.

It means that if your storage rules contain a rule that disallows you to read an object from your bucket, your app/web won’t be able to read it (using the client SDK), even though the object could be set to publicly accessible via IAM and has a public URL.

And vice versa — if an object is not publicly accessible (via IAM), but storage rules gives us permissions to read or write or delete, we’ll be able to perform these actions on the object via our client SDK.

Storage rules and Firebase rules in general are an important topic is worth writing about in a separate article, but there is one thing I wanted to mention as it was the driving idea behind this article:

You can’t use data from Firestore in storage rules. In case you wanted to base permissions to a file on a user role which is stored in your database in some collection, let me tell you: it is not possible (yet). The only possibility in this case would be to leverage user’s custom claims.

Conclusion

In the beginning I had a hard time to understand how this works and why it is so complicated. But after spending some time by playing around I am convinced that Firebase Storage is a great product. Even its complexity allows us to control access in all situations and on multiple levels.

Do you know some other possibilities to retrieve files from Storage? Have I forgotten something? Leave a comment below!

--

--

Peter Kracik
Firebase Developers

Senior Software developer & DevOps Coordination Ciricle Lead with experience in graphic design #frontend #backend #devops -> kracik.sk