9

My Firebase Storage getSignedUrl() download links work for a few days, then stop working. The error message is

SignatureDoesNotMatch
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.

Last summer there was a long discussion of this on GitHub but I don't see that a solution was reached.

I'm thinking of using getDownloadURL() from the front end instead of using getSignedUrl() from the back end. Is getDownloadURL() less secure then getSignedUrl()?

Here's my code, which is mostly copied from the documentation:

let audioType = 'mp3';
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
const bucket = storage.bucket('my-app.appspot.com');
var file = bucket.file('Audio/' + longLanguage + '/' + pronunciation + '/' + wordFileType);

  // Firebase Storage file options
  var options = {
    metadata: {
      contentType: 'audio/' + audioType,
      metadata: {
        audioType: audioType,
        longAccent: 'United_States',
        shortAccent: 'US',
        longLanguage: 'English',
        shortLanguage: 'en',
        source: 'Oxford Dictionaries',
        word: word
      }
    }
  };

  const config = {
    action: 'read',
    expires: '03-17-2025',
    content_type: 'audio/mp3'
  };

  function oedPromise() {
    return new Promise(function(resolve, reject) {
      http.get(oedAudioURL, function(response) {
        response.pipe(file.createWriteStream(options))
        .on('error', function(error) {
          console.error(error);
          reject(error);
        })
        .on('finish', function() {
          file.getSignedUrl(config, function(err, url) {
            if (err) {
              console.error(err);
              return;
            } else {
              resolve(url)
            }
          });
        });
      });
    });
  }
Dennis Alund
  • 2,916
  • 1
  • 13
  • 34
Thomas David Kehoe
  • 10,040
  • 14
  • 61
  • 100
  • Have you tried using an explicit service account? The bug you link to lists it as a solution. – Brandon Yarbrough Mar 28 '19 at 20:26
  • Yes, I have serviceAccount set to a local json file, and then admin.initializeApp calls the serviceAccount. I call admin.firestore and admin.auth, but I never call admin.storage, as you can see in the above code. Instead I do "const storage = new Storage();", coped from the Google documentation. Maybe I should use admin.storage instead of "new Storage()"? It's not clear to me where "new Storage()" gets a service account from. – Thomas David Kehoe Mar 28 '19 at 21:22
  • OK, I've switched from "const storage = new Storage();" to "const storage = admin.storage();". It works, ask me in a month whether the signed download URLs still work! And this morning I refactored my front end to use getDownloadURL() instead of relying on the back end to provide signed URLs. From reading the discussion on GitHub it sounds like signed URLs were intended for short-term use. The "v4" version can't have an expiration date beyond one week! And Google rotates service keys every week. I prefer signed URLs, as getDownloadURL() added hundreds of lines of complex code to my app. – Thomas David Kehoe Mar 28 '19 at 22:09

3 Answers3

8

The presigned URL will expire at the date you provide with the expires field.

However, the issue you are seeing here is that KMS keys for the admin SDK are rotated every 7 days. So if you create the presignedURL using the auto-provisioned storage() library, once the keys rotate your presigned URL will no longer be valid (because the key used to sign it is no longer valid). So your URL will be valid for less than or equal to 7 days depending on the age of the key.

Instead you need to not use the admin SDK and instead use the Google Cloud Storage npm module and initialize it with a service account json.

const storage = new Storage({keyFilename: "key.json"});

or

`const storage = new Storage({credential: require("key.json")});

David D.
  • 143
  • 2
  • 4
5

The maximum duration of a Google Cloud Storage Signed URL is 7 days. But it can also be shorter. Never longer. I guess the Firebase Storage has the same limit.

Peter Fortuin
  • 5,041
  • 8
  • 41
  • 69
  • 2
    I hesitate to give you the green check mark because it's not the answer I want, but there's every reason to believe you. The documentation (https://cloud.google.com/nodejs/docs/reference/storage/2.3.x/File#getSignedUrl) says that "expires" is "A timestamp when this link will expire," without saying anything about a seven day limit. The documentation examples have "expires: '03-17-2025'", implying that you can set the expiration date years in the future. But not matter what I do the download URLs expire in seven days. – Thomas David Kehoe Apr 24 '19 at 16:36
  • 6
    Is there any way to get a permanent download URL from a Google Cloud Function? I know how to use getDownloadURL() from the browser, but I can't get it to work in a Cloud Function. getDownloadURL() isn't a Node function. It seems odd to me that I can get a permanent download URL from the browser and a temporary download URL from the Cloud Function. Shouldn't it be the other way around? Browsers are transitory, no one will keep their browser open for a week. But when I upload files from a Cloud Function it's permanent, and the download URL should be permanent, – Thomas David Kehoe Apr 24 '19 at 19:04
  • If you enable public access on your object in Cloud Storage, you can use a direct URL to that object. Everybody with that URL can download the object. – Peter Fortuin Apr 30 '19 at 14:45
  • 1
    @ThomasDavidKehoe it does, please take a look at this link: https://cloud.google.com/storage/docs/access-control/signed-urls#example then searching for "7 days". You will see the limitation – Nguyễn Anh Tuấn Jun 30 '21 at 03:18
  • Ref to the 7 days limit https://cloud.google.com/storage/docs/access-control/signed-urls#example – Thach Lockevn Aug 16 '23 at 10:49
0

I've written a long answer to this question: [Get Download URL from file uploaded with Cloud Functions for Firebase. This question can be marked as a duplicate.



  [1]: https://stackoverflow.com/questions/42956250/get-download-url-from-file-uploaded-with-cloud-functions-for-firebase
Thomas David Kehoe
  • 10,040
  • 14
  • 61
  • 100
  • 1
    Ten days later the signed download URLs are dead. Problem not solved! – Thomas David Kehoe Apr 24 '19 at 16:31
  • Thomas David Kehoe - did you ever find a solution to this? This problem has been driving me nuts for a very long time. – barrylachapelle Jul 03 '19 at 13:02
  • @barrylachapelle, I wrote a long answer here: https://stackoverflow.com/questions/42956250/get-download-url-from-file-uploaded-with-cloud-functions-for-firebase I'm testing another idea someone suggested (using uuidv2 instead of uuidv4), ask me in a week if this worked! – Thomas David Kehoe Jul 08 '19 at 18:04
  • Could you not use node cron and simply run the script to regenerate the download URL every x days? – Joe Moore Aug 16 '22 at 13:10