0

I'm using an ESP32 to upload images to Firebase Storage, but via the underlying Google Cloud Storage JSON API. So, I'm authenticating as a user with the Firebase Rest API and then retrieving the ID token to use for the Storage upload.

But, I'm getting a 401 Unauthorized error when using that ID Token. However, directly replacing the ID token with an OAuth2.0 Token from the Playground, with the scope https://www.googleapis.com/auth/devstorage.read_write, works fine; I can see the images pop up in the Firebase Console.

I saw this question, where Doug mentions the functionality I'm trying to get but clearly have a bug with.

My code is as follows:

#include "Arduino.h"
#include "globalVars.h"
#include "esp_camera.h"
#include <ArduinoJson.h>

#include <HTTPClient.h>

void checkMotion()
{
    delay(10000);
    camera_fb_t *fb = esp_camera_fb_get();
    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    uint8_t *jpeg_buf = (uint8_t *)ps_malloc(fbLen);
    frame2jpg(fb, 2, &jpeg_buf, &fbLen);
    long currTime = millis();
    HTTPClient storageClient;
    String STORAGE_UPLOAD_PATH = NEW_OBJECT_BASE_PATH + String(currTime) + ".jpeg";
    storageClient.begin(STORAGE_UPLOAD_PATH);
    Serial.print("uploading to : ");
    Serial.println(STORAGE_UPLOAD_PATH);

    // Using the ID Token from Auth--doesn't work
    Serial.println(idToken); // successfully prints it out
    String bearerString = String("Bearer ") + String(idToken);
    storageClient.addHeader("Authorization", bearerString);

    // Using Playground token instead, and commenting the above--works!
    // storageClient.addHeader("Authorization", "Bearer MY_PLAYGROUND_TOKEN_IS_HERE");

    // Content type
    storageClient.addHeader("Content-Type", "image/jpeg");
    storageClient.addHeader("Content-Length", String(fbLen));
    int uploadResponseCode = storageClient.POST(jpeg_buf, fbLen);
    Serial.print("Response code: ");
    Serial.println(String(uploadResponseCode));
    
    String uploadResponseRaw = storageClient.getString();
    Serial.println("Received response:" + uploadResponseRaw);
    const size_t responseCapacity = JSON_OBJECT_SIZE(100);
    StaticJsonDocument<responseCapacity> dUploadResponse;
    DeserializationError deserializeResult = deserializeJson(dUploadResponse, uploadResponseRaw);
    Serial.println(deserializeResult.c_str());
    storageClient.end();
    free(jpeg_buf);
}

When using the idToken, this is the response I receive:

{
  "error": {
    "code": 401,
    "message": "Invalid Credentials",
    "errors": [
      {
        "message": "Invalid Credentials",
        "domain": "global",
        "reason": "authError",
        "locationType": "header",
        "location": "Authorization"
      }
    ]
  }
}

During testing I've also even my Firebase Storage rules to simply allow everything:

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

What am I missing? Thanks in advance.

Brian Chan
  • 160
  • 2
  • 7

1 Answers1

1

Firebase ID tokens are only usable through the Firebase SDKs. The Firebase backends then perform access control checks on the ID token against the security rules.

Access Cloud Storage through a GCP API bypasses the Firebase servers and security rules, and can for that reason not be accomplished with a Firebase ID token. You will have to use an OAuth 2 token instead.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807