2
votes

When I am trying to create / access Azure blob using Ad-hoc Shared Access Signature (SAS) I am getting "The remote server returned an error: (403) Forbidden." error. Can someone help me to identify, what is going wrong with this code.

    // Calling this function from Main().
    public void uploadToBlob()
    {
        string content = "Hello World - Content of the file";
        string fileName = "TestFile";
        UploadFileToAzureBlobStorage(content, fileName);
    }

    void UploadFileToAzureBlobStorage(string content, string fileName)
    {
        string storageKey = "SAS Key";
        string storageAccount = "Storage Account name";
        string containerName = "Container Name";
        string blobName = fileName;

        string method = "PUT";
        string sampleContent = content;
        int contentLength = Encoding.UTF8.GetByteCount(sampleContent);

        string requestUri = $"https://{storageAccount}.blob.core.windows.net/{containerName}/{blobName}";

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
        string now = DateTime.UtcNow.ToString("R");
        request.Method = method;
        request.ContentType = "text/plain; charset=UTF-8";
        request.ContentLength = contentLength;
        request.Headers.Add("x-ms-version", "2015-12-11");
        request.Headers.Add("x-ms-date", now);
        request.Headers.Add("x-ms-blob-type", "BlockBlob");
        request.Headers.Add("Authorization", AuthorizationHeader(method, now, request, storageAccount, storageKey, containerName, blobName));

        using (Stream requestStream = request.GetRequestStream())
        {
            requestStream.Write(Encoding.UTF8.GetBytes(sampleContent), 0, contentLength);
        }

        using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
        {
            if (resp.StatusCode == HttpStatusCode.OK)
            { }
        }
    }

    public string AuthorizationHeader(string method, string now, HttpWebRequest request, string storageAccount, string storageKey, string containerName, string blobName)
    {
        string headerResource = $"x-ms-blob-type:BlockBlob\nx-ms-date:{now}\nx-ms-version:2015-12-11";
        string urlResource = $"/{storageAccount}/{containerName}/{blobName}";
        string stringToSign = $"{method}\n\n\n{request.ContentLength}\n\n{request.ContentType}\n\n\n\n\n\n\n{headerResource}\n{urlResource}";
        HMACSHA256 hmac = new HMACSHA256(Encoding.ASCII.GetBytes(storageKey));
        string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
        String AuthorizationHeader = String.Format("{0} {1}:{2}", "SharedKey", storageAccount, signature);
        return AuthorizationHeader;
    }
2
For storage key, are you using account key or SAS token?Gaurav Mantri
Also, do you have the response headers?Cam Soper
@Guaurav Mantri, I am passing SAS token here.sdg
@Cam Soper, Here is the response :AuthenticationFailedServer failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:d4af7fcf-001e-0041-7532-4127e3000000 Time:2017-10-09T19:10:06.7310979ZRequest date header too old: 'Mon, 09 Oct 2017 17:40:52 GMT'sdg

2 Answers

2
votes

The code you're using above is needed when you're using an account key. This is when you would need to compute and include Authorization header in your request. If you're using a Shared Access Signature (SAS) token, you don't need to do all of this because SAS token already includes authorization information.

Assuming your SAS Token is valid and contain appropriate permissions to upload a file, your code becomes quite simple. Please see modified code below:

void UploadFileToAzureBlobStorage(string content, string fileName)
{
    string sasToken = "SAS Key";
    string storageAccount = "Storage Account name";
    string containerName = "Container Name";
    string blobName = fileName;

    string method = "PUT";
    string sampleContent = content;
    int contentLength = Encoding.UTF8.GetByteCount(sampleContent);

    string requestUri = $"https://{storageAccount}.blob.core.windows.net/{containerName}/{blobName}?{sasToken}";

    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
    request.Method = method;
    request.ContentType = "text/plain; charset=UTF-8";
    request.ContentLength = contentLength;
    request.Headers.Add("x-ms-blob-type", "BlockBlob");

    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(Encoding.UTF8.GetBytes(sampleContent), 0, contentLength);
    }

    using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
    {
        if (resp.StatusCode == HttpStatusCode.OK)
        { }
    }
}
0
votes

I do not know if this would work directly because I changed some things besides the connectionstring. But maybe it will help.

Note that in this case I am returning the path of the image with header defined, because it is unnecessary to download the image to the server, it is better to deliver directly to the client.

    public static string ChaveStringConexao { get; set; }        


    public static string ObterUrlTemporaria(string container, string nomeBlob, string nomeOriginal, bool paraDownload = false)
    {
        var blob = InstanciaContainer(container).GetBlockBlobReference(nomeBlob);
        var sasToken = string.Empty;

        var sasPolicy = new SharedAccessBlobPolicy
        {
            Permissions = SharedAccessBlobPermissions.Read,
            SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-15),
            SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(30)
        };

        var _sasHeader = new SharedAccessBlobHeaders
        {
            ContentDisposition = $"attachment; filename={nomeOriginal}"
        };

        if (paraDownload)
        {
            sasToken = blob.GetSharedAccessSignature(sasPolicy, _sasHeader);
        }
        else
        {
            sasToken = blob.GetSharedAccessSignature(sasPolicy);
        }

        return new Uri(blob.Uri, sasToken).AbsoluteUri;
    }



    public static CloudBlobContainer InstanciaContainer(string container)
    {
        var storageAccount = CloudStorageAccount.Parse(ChaveStringConexao);
        var blobClient = storageAccount.CreateCloudBlobClient();
        var blobContainer = blobClient.GetContainerReference(container);

        return blobContainer;
    }

ConnectionString example for webconfig:

<add name="AzureArmazenamento" connectionString="DefaultEndpointsProtocol=https;AccountName=*****;AccountKey=ZO78t1NKuiW32kKTBlm6bWRohREzYpmokpuFI4N**********************;EndpointSuffix=core.windows.net" />

Example usage:

var cnn = ConfigurationManager.ConnectionStrings["AzureArmazenamento"].ConnectionString;
AzureUtilitario.ChaveStringConexao = cnn;

var url = AzureUtilitario.ObterUrlTemporaria(container, file.key, file.OriginalName, download);

For upload you do not need anything different. The SAS issue addresses you in the return of the file:

    public static bool Salvar(string container, string nomeBlob, byte[] arquivo)
    {
        var blockBlob = InstanciaContainer(container).GetBlockBlobReference(nomeBlob);

        blockBlob.Properties.ContentType = Path.GetExtension(nomeBlob).ConverteParaContentType();

        using (var stream = new MemoryStream(arquivo))
        {
            blockBlob.UploadFromStream(stream);

            return true;
        }
    }