azureazure-blob-storageauthentication-error

Error uploading file to Azure Blob Storage: Server failed to authenticate the request


I am trying to implement an upload to Azure Storage. I am unable to use @azure/storage-blob package as it conflicts with every version of Node, including versions 16, 18, 20, etc. It always throws the error node:os module not found. So, I switched to a custom solution, but I am facing a major issue with the following error:

Error uploading file to Azure Blob Storage: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

Please note the following checks I have already done:

const fetch = require('node-fetch');
const crypto = require('crypto');
const { sanitize, getMediasBucketName } = require('../../utils');
const logger = require('../../utils/logger');

class AzureBlobStorage {
  constructor() {
    this.accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
    this.accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY;
    this.containerName = process.env.AZURE_CONTAINER_NAME;

    if (!this.accountName || !this.accountKey || !this.containerName) {
      throw new Error('Azure Storage account name, key, or container name is missing.');
    }

    // Set the endpoint URL for the Blob Storage container
    this.endpoint = `https://${this.accountName}.blob.core.windows.net/${this.containerName}`;
  }

  _generateAuthorizationHeader(method, path, headers) {
    const canonicalHeaders = Object.keys(headers)
        .filter(key => key.toLowerCase().startsWith('x-ms-'))
        .sort()
        .map(key => `${key.toLowerCase()}:${headers[key]}`)
        .join('\n');
    const stringToSign = [
      method,
      '',
      headers['Content-Type'] || '',
      headers['x-ms-date'],
      canonicalHeaders,
      `/${this.accountName}/${this.containerName}${path}`
    ].join('\n');

    // Generate the signature using HMAC-SHA256
    const decodedKey = Buffer.from(this.accountKey, 'base64');
    const signature = crypto.createHmac('sha256', decodedKey).update(stringToSign).digest('base64');

    const authorizationHeader = `SharedKey ${this.accountName}:${signature}`;
    return authorizationHeader;
  }

  async storeFileInDirectory({
                               projectName,
                               fileName,
                               contentBody,
                               contentType,
                               mediaType,
                             }) {
    try {

      const safeFileName = sanitize(fileName);
      const filePath = `/${projectName}/${mediaType}/${safeFileName}`;
      const url = `${this.endpoint}${filePath}`;
      const headers = {
        'x-ms-date': new Date().toUTCString(),
        'Content-Type': contentType,
        'Content-Length': contentBody.length,
        'x-ms-blob-type': 'BlockBlob',
      };
      headers['Authorization'] = this._generateAuthorizationHeader('PUT', filePath, headers);

      const response = await fetch(url, {
        method: 'PUT',
        headers: headers,
        body: contentBody,
      });

      if (!response.ok) {
        throw new Error(`Error uploading file to Azure Blob Storage: ${response.statusText}`);
      }

      logger.info(`File uploaded to Azure: ${filePath}`);

      return {
        filePath
      };
    } catch (e) {
      logger.error('Error uploading file to Azure Blob Storage', {
        errorMessage: e.message,
        stack: e.stack,
        params: { projectName, fileName, mediaType },
      });
      return null;
    }
  }
}

Solution

  • Error uploading file to Azure Blob Storage: Server failed to authenticate the request.

    You can use the below code which will upload the file with path in the Azure Blob Storage using JavaScript.

    Code:

    const fetch = require('node-fetch-commonjs');
    const CryptoJS = require('crypto-js');
    
    class AzureBlobStorage {
      constructor() {
        this.accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
        this.accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY;
        this.containerName = process.env.AZURE_CONTAINER_NAME;
    
        if (!this.accountName || !this.accountKey || !this.containerName) {
          throw new Error('Azure Storage account name, key, or container name is missing.');
        }
    
        // Set the endpoint URL for the Blob Storage container
        this.endpoint = `https://${this.accountName}.blob.core.windows.net/${this.containerName}`;
      }
    
      _generateAuthorizationHeader(method, path, headers) {
        // Generate canonical headers (x-ms- headers sorted alphabetically)
        const canonicalHeaders = Object.keys(headers)
          .filter((key) => key.toLowerCase().startsWith('x-ms-'))
          .sort()
          .map((key) => `${key.toLowerCase()}:${headers[key]}`)
          .join('\n');
    
        // Generate canonical resource
        const canonicalResource = `/${this.accountName}/${this.containerName}${path}`;
    
        // Construct string to sign
        const stringToSign = [
          method, // HTTP method
          headers['Content-Encoding'] || '', // Content-Encoding
          headers['Content-Language'] || '', // Content-Language
          headers['Content-Length'] || '', // Content-Length
          headers['Content-MD5'] || '', // Content-MD5
          headers['Content-Type'] || '', // Content-Type
          '', // Date (not used with x-ms-date)
          headers['If-Modified-Since'] || '',
          headers['If-Match'] || '',
          headers['If-None-Match'] || '',
          headers['If-Unmodified-Since'] || '',
          headers['Range'] || '',
          canonicalHeaders, // Canonicalized headers
          canonicalResource // Canonicalized resource
        ].join('\n');
    
        // Sign the string with the account key
        const decodedKey = CryptoJS.enc.Base64.parse(this.accountKey);
        const signature = CryptoJS.HmacSHA256(stringToSign, decodedKey).toString(CryptoJS.enc.Base64);
    
        // Return the authorization header
        return `SharedKey ${this.accountName}:${signature}`;
      }
    
      async storeFileInDirectory({ projectName, fileName, contentBody, contentType, mediaType }) {
        try {
          // Sanitize file name to avoid invalid characters
          const safeFileName = fileName.replace(/[^a-zA-Z0-9._-]/g, '_');
    
          // Construct file path and full URL
          const filePath = `/${projectName}/${mediaType}/${safeFileName}`;
          const url = `${this.endpoint}${filePath}`;
    
          // Headers required for the PUT request
          const headers = {
            'x-ms-date': new Date().toUTCString(),
            'Content-Type': contentType,
            'Content-Length': Buffer.byteLength(contentBody),
            'x-ms-blob-type': 'BlockBlob',
            'x-ms-version': '2020-04-08', // Use a valid API version
          };
    
          // Add the Authorization header
          headers['Authorization'] = this._generateAuthorizationHeader('PUT', filePath, headers);
    
          // Make the PUT request to Azure Blob Storage
          const response = await fetch(url, {
            method: 'PUT',
            headers: headers,
            body: contentBody,
          });
    
          if (!response.ok) {
            throw new Error(`Error uploading file to Azure Blob Storage: ${response.status} - ${response.statusText}`);
          }
    
          console.log(`File uploaded successfully to Azure Blob Storage: ${filePath}`);
          return { filePath };
        } catch (e) {
          console.error('Error uploading file to Azure Blob Storage', {
            errorMessage: e.message,
            stack: e.stack,
            params: { projectName, fileName, mediaType },
          });
          return null;
        }
      }
    }
    
    // Example usage
    (async () => {
      const azureStorage = new AzureBlobStorage();
    
      const result = await azureStorage.storeFileInDirectory({
        projectName: 'exampleProject',
        fileName: 'example.txt',
        contentBody: 'This is an example file content.', // Replace with actual file content as a string
        contentType: 'text/plain', // Replace with actual MIME type
        mediaType: 'images',
      });
    
      console.log(result);
    })();
    

    Output:

    PS C:\> node example.js
    File uploaded successfully to Azure Blob Storage: /exampleProject/images/example.txt
    { filePath: '/exampleProject/images/example.txt' 
    

    enter image description here

    Portal: enter image description here