Search code examples
javascriptreactjsfirebasefirebase-storagefirebase-security

setting firebase storage rule to watch for a piece of state in React Firebase


Is it possible to set a firebase storage rule to watch the value of a piece of state?

I am not using firebase auth for my app I just want to use a bucket for file storage. I have a state variable within my app:

  const [state, setState] = useState({
    currentUser: null,
    isAuthed: false
  });

If the user is authenticated the isAuthed value will flip to true. Therefore would it be possible to write a rule set that looks as so:

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

Solution

  • Your post raises two questions:
    How to pass data to storage rules?
    How to check for authentication status without using firebase authentication?

    ✉️ Passing data to storage rules

    File path

    You could save your file to the path /userfiles/authenticated/... to signal that the file was uploaded by an authenticated user. In the storage rule, you have access to the path through the match clause:

    match /userfiles/authenticated/{allPaths=**} {
      allow read, write: if true;
    }
    

    Custom metadata

    When uploading a file you can set custom metadata this way:

    const metadata = { customMetadata: { isAuthed: true } };
    const uploadTask = uploadBytes(storageRef, file, metadata);
    

    Then you can read the metadata in the storage rules:

    match /{allPaths=**} {
      allow read, write: if request.resource.metadata.isAuth == true;
    }
    

    Custom claims or custom tokens

    Custom claims or custom tokens allow assigning data to a user in a secure way, this data is then passed to the storage rule. Custom claims necessitate using firebase authentication, but custom tokens allow you to assign a token from your server without using firebase authentication. To read the data:

    match /{allPaths=**} {
      allow read, write: if request.auth.token.isAuth == true;
    }
    

    🔒 Checking authentication status

    Use custom token

    The easiest way to ensure only authenticated users can upload is through custom claims or custom token, as detailed above.

    Cryptographic trick

    ⚠️ For fun only, use at your own risks
    Let's roll our own crypto protocol to have a secure way of allowing upload only to authenticated users. NB: this does not prevent read access because we cannot provide metadata.

    1- An user requests an upload token from your server:

    const crypto = require("crypto");
    const SECRET = "S3CRET"; // a secret shared by your server and security rules
    
    // If the user is authenticated, send them this upload token:
    const nonce = crypto.randomBytes(9).toString('base64');
    const data = `${nonce},${filepath},${SECRET}`
    const token = { nonce, hash: crypto.createHash('sha256').update(data).digest('base64') };
    

    2- You pass the upload token to the storage rule via the file path or custom metadata as described above

    3- In the storage rule, you validate the hash:

    match /{allPaths=**} {
      allow read: if true;
      allow write: if verifyHash(request, allPaths);
    }
    
    function verifyHash(request, path){
      let nonce = request.resource.metadata.nonce;
      let hash = request.resource.metadata.hash;
      let hash2 = hashing.sha256(nonce + "," + path + ",S3CRET")).toBase64();
      return hash == hash2; 
    }
    

    4- profit: only users who have an upload token can upload a file, as a bonus you also enforce the file path, and you could also enhance the token with a timestamp, and enforce some kind of rate limit.