0
votes

My front-end application (Angular 8) needs to be able to download files from multiple storage providers (currently Azure Blob Storage and Autodesk OSS). I would like the front-end to download directly from the provider rather than having to download first on my back-end. I would like to prevent having the front-end care about the specific providers, so I'm trying to avoid using provider-specific SDKs on the front-end (e.g. @azure/storage-blob) and instead retrieve a pre-authenticated URL from my back-end and then just download from there.

I'm using the following code to retrieve metadata about file (url, filename, and content type), do a GET request to the retrieved (pre-authenticated) URL to download the data, then use saveAs (from the file-saver package) to save to disk:

  downloadDocument(id: string) {
    this.getDocumentFileInfo(id).pipe(
      concatMap(fileInfo => {
        const headers = new HttpHeaders().set('Accept', fileInfo.contentType);
        return combineLatest(this.http.get(fileInfo.url, { responseType: 'arraybuffer',
          headers: headers }), of(fileInfo));
      })
    )
    .subscribe(latest => {
      const [data, fileInfo] = latest;
      const blob = new Blob([data], { type: fileInfo.contentType });
      saveAs(blob, fileInfo.name);
    });
  }

  private getDocumentFileInfo(id: string): Observable<DocumentFileInfo> {
    return this.http.get<Document>(`${this._config.apiUrl}documents/${id}/fileinfo`, {withCredentials: true}).pipe(
      catchError((error: any) => this.handleError(error))
    );
  }

When downloading from Azure Blob Storage using this code, I get a 403 response with the following message:

Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

If I copy the URL that is logged from browser dev tools and just paste into the address bar of new tab, the file downloads fine, so it doesn't seem to be a problem with the SAS. I've also setup CORS for my storage account to (for now) allow all origins, all methods, and all headers.

What am I missing?

2
Have you tried encoding or decoding the query string (which includes the SAS token) either in Angular or on the server side before attempting to download? I’ve seen odd issues like that before and your browser may be handling it better than the Angular httpclient – Alex
Thanks Alex. Per your suggestion, I tried using encodeURI() on the URL and then I tried decodeURI(). It still didn't work when using encodeURI(), but it did with decodeURI()! However, after having success I removed the decodeURI() and it still worked. I'm not sure if it was a CORS setting taking a while to take effect, something different about SAS tokens I'm getting now (time-related?), or just something dumb I've overlooked. It's working now, though, and I'll try decodeURI() again if the problem happens again. Thanks for your help! – devbmc
Summary the comment as reply. You can accept it as answer to help others who have same problem. – Joey Cai

2 Answers

0
votes

The query string which includes SAS token encode special characters in the URL.

So, you need to use decodeURI() on the URL.

0
votes

In the end, I'm not really sure what fixed the problem. Per Alex's suggestion, I first tried running the URL through encodeURI(), which didn't fix the issue. I then I tried decodeURI(), which did seem to fix the issue. However, after having success I removed the decodeURI() just to double-check, and it still worked.

So, I'm not sure if it was a CORS setting taking a while to take effect, something different about SAS tokens I'm getting now (time-related?), or just something dumb I've overlooked. It's working now, though, and I'll try decodeURI() again if the problem happens again.