Search code examples
c#http-status-code-403azure-blob-storageazure-rest-api

Azure BLOB Storage REST API Uploading a file throws 403 - Forbidden


I am trying to upload a PDF to a Azure Blob Storage Container using Share Access Key (SAS). Code was working fine, but suddenly it started throwing the 403 - Forbidden Exception.

Response Status Message:

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

As mentioned in this link, one of the possible causes for 403 could be expired a shared access key, hence I refreshed the key and tried with the new one, still no luck.

I am invoking the UploadBlobToStorageContainer method for a Console Application (.NET Framework 4.6.2)

CODE:

    public string AzureStorageAccountName { get; set; }

    public string AzureStorageAccessKey { get; set; }

    public string X_MS_VERSION
    {
        get
        {
            return "2017-04-17";
        }
    }

    public string X_MS_CLIENT_REQUEST_ID
    {
        get
        {
            return _x_ms_client_request_id;
        }
    }

    public string BaseURI
    {
        get
        {
            return string.Format("https://{0}.blob.core.windows.net/", AzureStorageAccountName);
        }
    }

    private bool UploadBlobToStorageContainer(string filePath, string targetFolderPath, string containerName)
    {
        bool isUploaded = false;
        try
        {                
            FileInfo fileInfo = new FileInfo(filePath);
            long contentLength = fileInfo.Length;
            long range = contentLength - 1;
            string method = "PUT";
            string contentType = "application/pdf";
            string blobName = fileInfo.Name;
            string blobType = "BlockBlob";
            string dateString = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
            string blobURI = BaseURI + containerName + "/" + blobName;
            string xmsHeader = $"x-ms-blob-type:{blobType}\nx-ms-date:{dateString}\nx-ms-version:{X_MS_VERSION}";
            string resHeader = $"/{AzureStorageAccountName}/{containerName}/{blobName}";

            if (!string.IsNullOrWhiteSpace(targetFolderPath))
            {
                blobName = targetFolderPath + "/" + fileInfo.Name;
            }
            
            if (WebRequest.Create(blobURI) is HttpWebRequest request)
            {
                request.Method = method;
                request.ContentLength = contentLength;
                request.Headers.Add("x-ms-blob-type", blobType);
                request.Headers.Add("x-ms-date", dateString);
                request.Headers.Add("x-ms-version", X_MS_VERSION);                    
                request.Headers.Add("Authorization", GetAuthorizationHeader(method, xmsHeader, resHeader, contentType, contentLength));

                using (Stream requestStream = request.GetRequestStream())
                {
                    byte[] fileContents = null;
                    using (FileStream fs = fileInfo.OpenRead())
                    {
                        fileContents = new byte[fs.Length];
                        fs.Read(fileContents, 0, fileContents.Length);
                        fs.Close();
                    }
                    requestStream.Write(fileContents, 0, fileContents.Length);
                }

                if (request.GetResponse() is HttpWebResponse response)
                {
                    if (response.StatusCode == HttpStatusCode.Created)
                    {
                        isUploaded = true;
                    }
                    else
                    {
                        isUploaded = false;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            if (ex is WebException wex)
            {
                StringBuilder sb = new StringBuilder();
                if (wex.Response is HttpWebResponse exr)
                {
                    sb.Append("StatusCode: " + exr.StatusCode + " - ");
                    sb.Append("Description: " + exr.StatusDescription + " - ");
                }
                sb.Append("ErrorStatus: " + wex.Status);
                Log.LogMessage(LogLevel.ERROR, "AzureBlobApi: UploadBlobToContainer: File upload failed. Reason: " + sb.ToString());
            }
            Log.LogException(ex);
        }
        return isUploaded;
    }

    private string GetAuthorizationHeader(string method, string xmsHeader, string resHeader, string contentType, long contentLength)
    {
        //Do NOT REMOVE THE \n. It is a request header placeholder
        /*
            GET\n //HTTP Verb
            \n    //Content-Encoding
            \n    //Content-Language
            \n    //Content-Length (empty string when zero)
            \n    //Content-MD5
            \n    //Content-Type
            \n    //Date
            \n    //If-Modified-Since 
            \n    //If-Match
            \n    //If-None-Match
            \n    //If-Unmodified-Since
            \n    //Range
            x-ms-date:Fri, 26 Jun 2015 23:39:12 GMT
            x-ms-version:2015-02-21 //CanonicalizedHeaders
            /myaccount/mycontainer\ncomp:metadata\nrestype:container\ntimeout: 20    //CanonicalizedResource
        */
        
        string strToSign = $"{method}\n\n\n\n{contentLength}\n\n\n\n\n\n\n\n{xmsHeader}\n{resHeader}";

        string signatureString = GetHashedString(strToSign, AzureStorageAccessKey);

        string authorizationHeader = string.Format(
             CultureInfo.InvariantCulture,
             "{0} {1}:{2}",
             "SharedKey",
             AzureStorageAccountName,
             signatureString);

        return authorizationHeader;
    }

    private string GetHashedString(string signingString, string accessKey)
    {
        string encString = "";
        try
        {
            byte[] unicodeKey = Convert.FromBase64String(accessKey);
            using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
            {
                byte[] dataToHmac = Encoding.UTF8.GetBytes(signingString);
                encString = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
            }
        }
        catch (Exception ex)
        {
            Log.LogMessage(LogLevel.ERROR, $"AzureBlobApi: GetHashedString: Exception getting hash string {ex.Message}");
        }
        return encString;
    }

Headers

PUT



518262







x-ms-blob-type:BlockBlob
x-ms-date:Sun, 30 Aug 2020 08:43:31 GMT
x-ms-version:2017-04-17
/mystorage/documentcontainer/cricket/test document.pdf

Any help would be really appreciated.

Thanks Raghunathan S


Solution

  • Found the solution for my problem. When i ran the request through POSTMAN, figured out that then resource uri in the header is URLEncoded, which i didn't do in my code and the name of the file that i am trying to upload as a space in-between "test document.pdf"

    Response from PostMan

    'PUT
    
    
    518262
    
    application/pdf
    
    
    
    
    
    
    x-ms-blob-type:BlockBlob
    x-ms-date:Mon, 31 Aug 2020 04:41:25 GMT
    x-ms-version:2019-12-12
    /mystorage/documentcontainer/cricket/test%20document.pdf'
    

    whereas the header generated for signing from my code is

    PUT
    
    
    
    518262
    
    
    
    
    
    
    
    x-ms-blob-type:BlockBlob
    x-ms-date:Sun, 30 Aug 2020 08:43:31 GMT
    x-ms-version:2017-04-17
    /mystorage/documentcontainer/cricket/test document.pdf
    

    When i made the following change in my code, by URL encoding the file name

    string blobName = Uri.EscapeUriString(fileInfo.Name);
    

    it worked and the files got uploaded successfully.

    Thanks Guys for the Help.