firebasegoogle-cloud-platformgoogle-cloud-firestorefirebase-authenticationfirebase-storage

Firebase Storage Files Accessible via Browser Despite Security Rules


I'm working on a Jetpack Compose app where users can upload images to Firebase Storage, and I’m storing the image URLs in Firestore. I've set up the following security rules for Firebase Storage:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

My upload function looks like this:

override suspend fun uploadFile(file: File, folderName: String): String = withContext(Dispatchers.IO) {
    val uniqueId: UUID = UUID.randomUUID()
    val objectKey = "$folderName/$uniqueId.${file.extension}"
    val storageRef: StorageReference = storage.reference.child(objectKey)

    storageRef.putFile(file.toUri()).await()
    
    storageRef.downloadUrl.await().toString()
}

Even after implementing these security rules, the uploaded files can still be accessed through a direct URL in the browser.

I want to ensure that these files can only be accessed through my app. Is there a way to ensure that files in Firebase Storage can only be accessed by authenticated users within my app?

I learned that download URLs are accessible over the internet, and I need to use the Firebase SDK instead of relying on these URLs. However, I’m not sure how to implement that.


Solution

  • When you upload a file to the Cloud Storage for Firebase, there are two options available for accessing the uploaded file.

    1. You can access the file using a URL that starts with "https://", for example:

      https://firebasestorage.googleapis.com/v0/b/your-project.appspot.com/file.jpg&token=token

    2. Or using a URL that starts with "gs://". This type of URL is a Google Cloud Storage native URL, which, unfortunately, is not recognized by many libraries.

    When you access the Firebase Console you can find both types of URLs. So click on a file and check the "File location" section panel, on the right-hand side of the page.

    If you write to Firestore the download URL is returned by the below line of code:

    storageRef.downloadUrl.await().toString()
    

    Then most likely the download URL looks like the one in the first example. As you can see, the URL has at the end a token that uniquely identifies the file in the Storage Bucket. This means that if you know the token, the URL is publicly available.

    However, the token acts as a security measure to restrict access only to those users who know the token. It's quite impossible to predict such a token because it's a random UUIDv4 that is automatically generated whenever a file is uploaded to the Storage Bucket.

    Such URLs cannot be restricted using Firebase Storage Security Rules. As said before, these types of URLs are always public. Since they contain a token, these URLs are also known as "public, unguessable URLs". If you think that a particular token was compromised, you have a "revoke option" inside the Firebase Console if you need that.

    So the Security Rules will only apply to URLs that don't have a token included in the URL, for example:

    So I recommend not worrying about such URLs (with access tokens) as it's unlikely to be guessed. Besides that, as I understand from your question, the URLs only exist in Firestore. So this is pretty good because you can write Firestore Security Rules to ensure that only users that are authorized can read those URLs.

    Edit:

    The problem is that once the download URL is generated and the images are displayed, this URL can be visible in the network call(correct me if I'm wrong). Users could potentially share these URLs publicly, making them accessible to unauthenticated users, which creates a risk of URL compromise.

    You're 100% correct. If you want to avoid that, then I recommend you access the files that start with "gs://". In this case, there is no token involved. Or, if you want the file to be strictly available to a single user, then I recommend downloading the file from Storage through the Firebase SDK, and displaying it inside your app's code.

    I'd like to know if there's an expiry time for the tokens, or if we can set an expiry time for them.

    No, Firebase Storage access tokens do not expire. As mentioned before, a token may be revoked from the Firebase Console, which would invalidate the URL that uses it.

    Also, does the getDownloadUrl method generate a new URL with each request, or does it return the same URL each time?

    Even if you upload the same file, a new URL with a different token will be generated.