Image source: Google, firebase.google.com

This is written in english because...

I have to admit, I was excited and scared at the same time, when I got tasked with building a webapp based on Google Firebase

TL;DR

All the API endpoints and logic was provided by the folks who programmed the Android app (disclosure of the shop, when the project is finished). They did a great job designing the API!

The web backend is used to add new users and create reports from checklists.

Working with JSON endpoints is no problem. Confusion arose when I had to deal with Firebase Authentication and lately with Firebase Storage.

If you look at the guides, it's all centered around Javascript, iOS and Android.

There are some libraries for Python and also PHP. Unfortunately, when I started the project the kreait/firebase-php: Firebase Admin PHP SDK wasn't complete yet. So I was rolling out my own wrapper und tried to make sense ouf Firebase Authentication and their REST API. It took some time to understand, that the Firebase Auth Service just works with the Google Identity Toolkit. Who knew... And yes, they recommend to move to the Firebase JS SDK, but under the hood they still call https://www.googleapis.com/identitytoolkit/v3/....

With having solved the identity crisis and being able to obtain and refresh a valid token to sign all my API calls, the next big step was to access Firebase Storage.

It took me two days to figure out what was going on, trying to make sense of the documentation and one year old stack overflow solutions that never fit my use-case.

Solution

One of the API endpoints returns Firebase Storage objects like this:

"fileReferences": [
    {
        "id": "",
        "name": "images\/-s0m31D\/picture.jpg",
        "mediaLink": "https:\/\/www.googleapis.com\/download\/storage\/v1\/b\/BUCKET.appspot.com\/o\/images%2F-s0m31D%2Fpicture.jpg?generation=1537959346600572&alt=media",
        "selfLink": "https:\/\/www.googleapis.com\/storage\/v1\/b\/BUCKET.appspot.com\/o\/images%2F-s0m31D%2Fpicture.jpg",
        "updated": 1537959346,
        "size": 7759448
    }
],

The first thing I did was grabbing the value of fileReferences.0.mediaLink and send a request with my Firebase Auth token in the header. This ended in "Permission denied" notices. Even after the devs made the Firebase Storage publicly available.

You can use the Google Cloud Storage APIs to access files uploaded via Firebase SDKs for Cloud Storage, especially to perform more complex operations, such as copying or moving a file, or listing all the files available at a reference.

It's important to note that these requests use Google Cloud Storage ACLs or Project Level IAM, rather than Firebase Authentication and Storage Security Rules.

-- Source

WHAT?!?! "Project Level IAM, rather than Firebase Authentication and Storage Security Rules"? What does that even mean?

The documentation of the underlying Google Cloud Storage REST API didn't help much. It showed it must be possible, but it rejected my Firbase Auth and instead talked about Google Serive Accounts etc. I almost gave up.

The SDK's as easy as they are, they are black boxes. If you don't reverse engineer them, you don't know what happens inside, when you call something like storageRef.child('images/stars.jpg').getDownloadURL().

But I got a hint when I obtained a working url from one of the media objects and when I jumped through the minimized Firebase Javascript SDK and checked the browser network calls.

The SDK builds an url after this scheme, to obtain media:

http[s]://firebasestorage.googleapis.com/<api-version>/b/<bucket>/o/<object-path>

To be precise, this is how to create the final media url:

printf('https://firebasestorage.googleapis.com/v0/b/storageBucket.appspot.com/o/%s', urlencode('projects/-s0m31D/picture.jpg'));

storageBucket is the value you get when you setup Firebase.

A call (signed with my Firebase Auth Bearer Token!) to https://firebasestorage.googleapis.com/v0/b/bucket.appspot.com/o/projects%2F-s0m31D%2Fpicture.jpg returns the following meta data:

{
    "name": "projects/-id/logo.png",
    "bucket": "bucket.appspot.com",
    "generation": "1537960188874518",
    "metageneration": "1",
    "contentType": "image/png",
    "timeCreated": "2018-09-26T11:09:48.874Z",
    "updated": "2018-09-26T11:09:48.874Z",
    "storageClass": "STANDARD",
    "size": "40437",
    "md5Hash": "MxkOU+6feyYtdEAgKbDgp5A==",
    "contentEncoding": "identity",
    "contentDisposition": "inline; filename*=utf-8''picture.jpg",
    "crc32c": "o89Y9dQ==",
    "etag": "CJae8pXE2N0CEAE=",
    "downloadTokens": "32c339ff9-7e4a-42a2-890a-428f8f45d378"
}

EUREKA there it is, the downloadTokens.

Tests in Postman

Unauthenticated request in Postman

Authenticated request in Postman

Now there are two ways to download the image (or to display it in the browser):

1. Authentificate the call with the Firebase Auth Bearer Token

curl -X GET \
-H "Authorization: Bearer [TOKEN]" \
-o "picture.jpg" \
"https://firebasestorage.googleapis.com/v0/b/bucket.appspot.com/o/projects%2F-s0m31D%2Fpicture.jpg?alt=media"

or

2. Share publicly with the downloadTokens

curl -X GET \
-o "picture.jpg" \
"https://firebasestorage.googleapis.com/v0/b/bucket.appspot.com/o/projects%2F-s0m31D%2Fpicture.jpg?alt=media&token=32c339ff9-7e4a-42a2-890a-428f8f45d378"

With the token=xyz URL parameter, the image is now available to everyone who has access to the complete link.

This is how to download objects from the Firebase Storage using your Firebase Authentication.

If you think I'm wrong or you want to add some information, please do at this Stack Overflow question