3

On my Firebase app, I'd like to know the recommended way to make a server-side request to Cloud Storage for Firebase on behalf of a signed-in user (authenticated via ID token), with security rules applied to the request (i.e. not with the Admin SDK).

The overall app flow currently looks like this:

  • User signs in on the client and makes a request to a Google Cloud API gateway, sending their ID token in the request header
  • API gateway authenticates user and forwards request to different endpoints in the backend API:
    • If it's a request for Firebase Authentication, the API uses Auth Admin SDK to handle it directly
    • If it's for Firestore, the API uses the x-forwarded-authorization request header (which contains the ID token originally provided by the client) forwarded by API gateway to then make a request to Firestore's REST API, so that the request can be evaluated by security rules.

For Cloud Storage, I'd like to do something similar to the Firestore case above, but there isn't a Firebase-specific REST API available in the docs. It's possible to just let the client make requests directly to Firebase (as suggested in this answer), but would prefer to keep the logic in the backend.

Are there alternatives to doing this for Storage? Please feel free to also point out if there are better ways of handling the Firebase Auth and Firestore cases mentioned above. Thanks!

EDIT: Adding more possible solutions as I find them

  • Doug's answer here and Frank's answer here suggest using the Cloud Storage REST API, where the app generates an OAuth token for the user and makes the request
  • This answer here mentions it's possible to pass an auth=IDTOKEN query parameter to the Firebase REST API:

Once you have an ID token, you can pass that to the REST API via the auth query parameter to authenticate a request. The request respects Firebase Security Rules as if the end user logged into the client was making the request.

Jason
  • 115
  • 6

3 Answers3

1

There is no way to enforce Firebase's security rules for Cloud Storage when using the Admin SDK. Firebase doesn't provide a documented REST API for Cloud Storage, and I doubt the Google Cloud REST API for Storage accepts Firebase ID tokens.

So I don't really think you have good options to do this from the server on behalf of your users. The two options I can think of:

  1. Pass the necessary information back to the client, and let that access the data through the Firebase SDK - in which case the security rules are enforced.
  2. Replicate the necessary security logic in your Cloud Functions code.
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks for the info Frank. You're right that the Google Cloud REST API doesn't accept Firebase ID tokens. What I did in the end was to rearrange things and do it directly from the client instead (without passing from the server). Will add an answer below to detail it. – Jason Jan 11 '21 at 02:09
1

Edit 6 Jan 2020: This method has had a couple of days testing under heavy-load. See bottom for some minor new requirements.

Original response: This isn't an answer, so much as an in-ecosystem workaround (that works for me!)

Basically, I have a similar-ish use-case with regards an offline-first/potentially insecure iot device that needs auth based storage access with security rules. This morning I took a peek at the Firebase js sdk repo and noted that a native XHR API is the main piece missing from a working node implementation.

I detailed my workaround on this github issue ... included here for convenience:

.. patching the storage library with an XHR polyfill actually appears to work (Node 15 / minimal testing so far). Specifically I've installed xmlhttprequest-ssl and imported it at the top of the storage module (in my case line 5 in @firebase/storage/dist/index.cjs.js):

var XMLHttpRequest = require("xmlhttprequest-ssl").XMLHttpRequest;

Back in the app I import firebase/storage as normal, then just load files from the local file system and upload as per the standard Firebase API:

import { firebase } from "@firebase/app";
import "@firebase/storage";

/* include Firebase setup, etc. */

const filename = 'offline-user-generated-image.jpg';
const file = fs.readFileSync(filename); // for brevity only
const ref = firebase.storage().ref().child(`test/${filename}`);

// use the buffer's underlying arraybuffer 
ref.put(file.buffer, {
  contentType: 'image/jpg', // defaults to 'application/octet-stream'
  customMetadata: {
    uid: 'abc123', // for enhanced security rules
  },
})
.then(() => console.log('done'))
.catch(e => console.log(e));

Storage rules apply to uploads (woop!) and security tokens and custom metadata are applied as expected. Also tested with `putString'. This solves some fairly drastic workarounds for my use case, as it means I can keep my logic/auth/etc. entirely within the Firebase ecosystem. Keen to hear others thoughts.

Note: I'm using patch-package to ensure the patch stays intact across installs / upgrades.

Update 6 Jan 2020: Some extra bits and pieces

It turns out that there is an uncleared timeout in the storage package that causes hell with node processes (and is likely a resource hog in all environments) .. I've submitted a PR (pretty much a clearTimeout) that resolves the issue, but in the meantime this will also need to be manually patched unless you can safely process.exit(0) in your code.

som
  • 2,023
  • 30
  • 37
  • I believe you're referring to the client SDK yes? I wasn't using this in my server so I couldn't use it to validate requests against security rules previously. But when trying to do it from the client, your answer was helpful, together with this one: https://stackoverflow.com/questions/55671126/get-downloadurl-keeps-throwing-error-xmlhttprequest-is-not-defined-using-fireb – Jason Jan 11 '21 at 02:08
  • Yes, apologies for being a bit unclear there. As mentioned by @FrankvanPuffelen the client SDK is the only way you can hit storage rules. The above patch lets you use the client storage api in node, which should solve your problem. I've previously run both the admin and client sdks in node side-by-side without issue (to hit rtdb rules on behalf of user). In my current scenario I'm on an insecure node server (so unable to store admin credentials) and am using the patch above to give storage access with client sdk alone. – som Jan 11 '21 at 05:04
0

After considering the various options above (thanks Frank and som!), the best choice in my situation was to change things up and make Storage requests directly from the client. The 2 main reasons were:

  • It is the simplest to implement since there is already an SDK that allows you to do this
  • Interacting with Firebase Storage may involve large objects, and so user experience might suffer if there are too many network trips

When making the request from the client, som's answer above and this answer may come in useful if there's a XMLHttpRequest is not defined error

Jason
  • 115
  • 6