1
votes

I am trying to put a blob with Azure rest api. I made a "GET" request successfully but i had issues with "PUT" request. When I try to make "PUT" request i get a 403 error(Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.). I have seen same post in stackoverflow but it didn't help me. Any suggestions?

 string uri = string.Format("https://{0}.blob.core.windows.net/{1}/LibraryForm.png",                storageAccountName,containerName);
        Byte[] requestPayload =  File.ReadAllBytes(imagepath);
        using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Put, uri)
        { Content = (requestPayload == null) ? null : new ByteArrayContent(requestPayload) })
        {

            // Add the request headers for x-ms-date and x-ms-version.
            DateTime now = DateTime.UtcNow;
            httpRequestMessage.Headers.Add("x-ms-date", now.ToString("R", CultureInfo.InvariantCulture));
            httpRequestMessage.Headers.Add("x-ms-version", "2020-06-12");
            httpRequestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");
            httpRequestMessage.Headers.Add("x-ms-blob-content-type", "image/png");

            // Add the authorization header.
            httpRequestMessage.Headers.Authorization = GetAuthorizationHeader(
               storageAccountName, storageAccountKey, now, httpRequestMessage);
            // Send the request.
            using (HttpResponseMessage httpResponseMessage = await new HttpClient().SendAsync(httpRequestMessage))
            {
                if (httpResponseMessage.StatusCode == HttpStatusCode.OK)
                {
                    var str = httpResponseMessage.Content.ReadAsStringAsync().Result;
                    return str;
                }
            }
        }


      internal static AuthenticationHeaderValue GetAuthorizationHeader(string storageAccountName, string storageAccountKey, DateTime now,
    HttpRequestMessage httpRequestMessage, string ifMatch = "", string md5 = "")
    {
        // This is the raw representation of the message signature.
        HttpMethod method = httpRequestMessage.Method;
        String MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
                  method.ToString(),
                  (method == HttpMethod.Get || method == HttpMethod.Head) ? String.Empty
                    : httpRequestMessage.Content.Headers.ContentLength.ToString(),
                  ifMatch,
                  GetCanonicalizedHeaders(httpRequestMessage),
                  GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName),
                  md5);

        // Now turn it into a byte array.
        byte[] SignatureBytes = Encoding.UTF8.GetBytes(MessageSignature);

        // Create the HMACSHA256 version of the storage key.
        HMACSHA256 SHA256 = new HMACSHA256(Convert.FromBase64String(storageAccountKey));

        // Compute the hash of the SignatureBytes and convert it to a base64 string.
        string signature = Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));

        // This is the actual header that will be added to the list of request headers.
        // You can stop the code here and look at the value of 'authHV' before it is returned.
        AuthenticationHeaderValue authHV = new AuthenticationHeaderValue("SharedKey",
            storageAccountName + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes)));
        return authHV;
    }   
        
      private static string GetCanonicalizedHeaders(HttpRequestMessage httpRequestMessage)
    {
        var headers = from kvp in httpRequestMessage.Headers
                      where kvp.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase)
                      orderby kvp.Key
                      select new { Key = kvp.Key.ToLowerInvariant(), kvp.Value };

        StringBuilder sb = new StringBuilder();

        // Create the string in the right format; this is what makes the headers "canonicalized" --
        //   it means put in a standard format. http://en.wikipedia.org/wiki/Canonicalization
        foreach (var kvp in headers)
        {
            StringBuilder headerBuilder = new StringBuilder(kvp.Key);
            char separator = ':';

            // Get the value for each header, strip out \r\n if found, then append it with the key.
            foreach (string headerValues in kvp.Value)
            {
                string trimmedValue = headerValues.TrimStart().Replace("\r\n", String.Empty);
                headerBuilder.Append(separator).Append(trimmedValue);

                // Set this to a comma; this will only be used
                //   if there are multiple values for one of the headers.
                separator = ',';
            }
            sb.Append(headerBuilder.ToString()).Append("\n");
        }
        return sb.ToString();
    }   
        
    private static string GetCanonicalizedResource(Uri address, string storageAccountName)
    {
        StringBuilder sb = new StringBuilder("/").Append(storageAccountName).Append(address.AbsolutePath);

        // It will have more entries if you have more query parameters.
        NameValueCollection values = HttpUtility.ParseQueryString(address.Query);

        foreach (var item in values.AllKeys.OrderBy(k => k))
        {
            sb.Append('\n').Append(item).Append(':').Append(values[item]);
        }

        return sb.ToString().ToLower();

    }
1
Two things: 1) Why not use Storage SDK? and 2) Look at the response body of your error response. You should see the string to sign used by Azure Service in there. Compare that with your MessageSignature. Both of them should exactly match. - Gaurav Mantri

1 Answers

0
votes

If you want to upload a file to Azure Blob with rest API, please refer to the fooliwng code

I define a class to get ShareKey

internal static class AzureStorageAuthenticationHelper
    {
        
        internal static AuthenticationHeaderValue GetAuthorizationHeader(
           string storageAccountName, string storageAccountKey, DateTime now,
           HttpRequestMessage httpRequestMessage, string ifMatch = "", string md5 = "")
        {
            // This is the raw representation of the message signature.
            HttpMethod method = httpRequestMessage.Method;
            String MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
                      method.ToString(),
                      (method == HttpMethod.Get || method == HttpMethod.Head) ? String.Empty
                        : httpRequestMessage.Content.Headers.ContentLength.ToString(),
                      ifMatch,
                      GetCanonicalizedHeaders(httpRequestMessage),
                      GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName),
                      md5);

            // Now turn it into a byte array.
            byte[] SignatureBytes = Encoding.UTF8.GetBytes(MessageSignature);

            // Create the HMACSHA256 version of the storage key.
            HMACSHA256 SHA256 = new HMACSHA256(Convert.FromBase64String(storageAccountKey));

            // Compute the hash of the SignatureBytes and convert it to a base64 string.
            string signature = Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));

            // This is the actual header that will be added to the list of request headers.
            // You can stop the code here and look at the value of 'authHV' before it is returned.
            AuthenticationHeaderValue authHV = new AuthenticationHeaderValue("SharedKey",
                storageAccountName + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes)));
            return authHV;
        }

        
        private static string GetCanonicalizedHeaders(HttpRequestMessage httpRequestMessage)
        {
            var headers = from kvp in httpRequestMessage.Headers
                          where kvp.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase)
                          orderby kvp.Key
                          select new { Key = kvp.Key.ToLowerInvariant(), kvp.Value };

            StringBuilder sb = new StringBuilder();

            // Create the string in the right format; this is what makes the headers "canonicalized" --
            //   it means put in a standard format. http://en.wikipedia.org/wiki/Canonicalization
            foreach (var kvp in headers)
            {
                StringBuilder headerBuilder = new StringBuilder(kvp.Key);
                char separator = ':';

                // Get the value for each header, strip out \r\n if found, then append it with the key.
                foreach (string headerValues in kvp.Value)
                {
                    string trimmedValue = headerValues.TrimStart().Replace("\r\n", String.Empty);
                    headerBuilder.Append(separator).Append(trimmedValue);

                    // Set this to a comma; this will only be used 
                    //   if there are multiple values for one of the headers.
                    separator = ',';
                }
                sb.Append(headerBuilder.ToString()).Append("\n");
            }
            return sb.ToString();
        }

      
        private static string GetCanonicalizedResource(Uri address, string storageAccountName)
        {
            // The absolute path is "/" because for we're getting a list of containers.
            StringBuilder sb = new StringBuilder("/").Append(storageAccountName).Append(address.AbsolutePath);

            // Address.Query is the resource, such as "?comp=list".
            // This ends up with a NameValueCollection with 1 entry having key=comp, value=list.
            // It will have more entries if you have more query parameters.
            NameValueCollection values = HttpUtility.ParseQueryString(address.Query);

            foreach (var item in values.AllKeys.OrderBy(k => k))
            {
                sb.Append('\n').Append(item).Append(':').Append(values[item]);
            }

            return sb.ToString().ToLower();

        }
    }
  1. Uplaod

                FileInfo fileInfo = new FileInfo("D:\\sampleData\\readsample.jpg");
                string blobName = fileInfo.Name;
                string contentType = MimeMapping.GetMimeMapping(blobName);
                DateTime now = DateTime.UtcNow;
                string blobURI = string.Format("https://{0}.blob.core.windows.net/{1}/{2}", StorageAccountName, "test", blobName);
                using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Put, blobURI))
                {
                    httpRequestMessage.Headers.Add("x-ms-date", now.ToString("R", CultureInfo.InvariantCulture));
                    httpRequestMessage.Headers.Add("x-ms-version", "2020-06-12");
                    httpRequestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");

                    httpRequestMessage.Headers.Add("x-ms-blob-content-type", contentType);
                    httpRequestMessage.Content = new StreamContent(fileInfo.OpenRead());
                    httpRequestMessage.Headers.Authorization = AzureStorageAuthenticationHelper.GetAuthorizationHeader(
                StorageAccountName, StorageAccountKey, now, httpRequestMessage);

                    // Send the request.
                    using (HttpResponseMessage httpResponseMessage = await new HttpClient().SendAsync(httpRequestMessage))
                    {
                        // If successful (status code = 200), 
                        //   parse the XML response for the container names.
                        if (httpResponseMessage.StatusCode == HttpStatusCode.Created)
                        {
                          
                        Console.WriteLine("OK");
                         

                        }
                    }
                }

enter image description here

Besides, Using Azure SDK to implement upload progress is a simple way. Regarding how to use azure sdk, please refer to here.