Pulling files from Azurite in Unity fails

238 views Asked by At

I am trying to use an OBJ file from Azure storage in my unity scene, which I will run as an website application. I have azurite running and a container created with files in it. I can see the file in Azure Storage Explorer, under Emulator->Blob Container->MyContainer. I created Azurite running this docker compose script:

azurite:
    image: mcr.microsoft.com/azure-storage/azurite
    container_name: "azurite"
    hostname: azurite
    restart: always
    ports:
      - "10000:10000"
      - "10001:10001"
      - "10002:10002"

In Azure Storage Explorer, I can see this is my connection string:

AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;

And when I am in Azure Storage Explorer, and I copy the URL for my file, I can see that it is:

http://127.0.0.1:10000/devstoreaccount1/MyContainer/MyFile.jpg

In my Unity script, I call this code:

private string DownloadString(string url)
{
    using (WebClient client = new WebClient())
    {   
        client.Headers.Add("Authorization", "SharedKey devstoreaccount1:Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==");
        return client.DownloadString(url);
    }
}

and call it like this

private void myTest(){
     string objUrl = "http://127.0.0.1:10000/devstoreaccount1/MyContainer/MyFile.obj";
     string objData = DownloadString(objUrl);
}

But I get this error:

System.Net.WebException: The remote server returned an error: (403) Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature..

EDIT

@Venkatesan thank you so much for this very thorough response! I have tried to use your code and run it, but I am still getting the same 403 error. I have moved my code out into a console app, just for testing purposes. I am not sure if I am using the wrong account credentials or if there is an issue with Docker? I can see in docker that the container is getting the request, but it still fails.

For the Access-Key, I use the Key specified in MS Azure Storage Explorer, which I assume is the default key setup, since I did not specify it in my Docker Compose: Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==

For the container, I use the value of azurite as it is defined in my Docker Compose file. I did notice that when the Docker Compose ran, it did not create a Blob Container, so I created one and called that azurite as well.

And then for the URL, I just used the URL as defined when I right click on the image, in Storage Explorer, and copy the URL:

http://127.0.0.1:10000/devstoreaccount1/azurite/MyFile.obj

so my request looks like this:

using (WebClient client = new WebClient())
{   
   client.Headers.Add("Authorization", $"SharedKey devstoreaccount1:{authorization}");
   client.Headers.Add("x-ms-date", date);
   client.Headers.Add("x-ms-version", apiVersion);
            
 client.DownloadString("http://127.0.0.1:10000/devstoreaccount1/azurite/MyFile.obj");
}

But when the console app runs, and I create the signature and pass that in, it still fails with a 403 error.

2

There are 2 answers

0
jason On BEST ANSWER

I had to make the following changes and this worked:

+ "/{0}/{1}/{2}", Account, Container, blob); should change to + "/{0}/{0}/{1}/{2}", Account, Container, blob);

client.Headers.Add("Authorization", $"SharedKey devstoreaccount1:{authorization}"); should change to client.Headers.Add("Authorization", authorization);

1
Venkatesan On

System.Net.WebException: The remote server returned an error: (403) Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.

The above error occurs when you are passing an incorrect signature in the authorization header and also missing some headers.

In the DownloadString function you are passed a direct access key in the authorization header which is incorrect and also you need to add the date and x-ms-version.

The header should be like this:

x-ms-version: 2014-02-14 
x-ms-date: Fri, 26 Jun 2015 23:39:12 GMT
Authorization: SharedKey myaccount:ctzMq410TV3wS7upTBcuxxxxx=

According to this MS-Docs you need to pass only signature in the authorization header.

To get the signature, date and version from the access key you can use the below C# code

Code:

using System;
using System.Globalization;
using System.Net;
using System.Security.Cryptography;

class Program
{
    static void Main(string[] args)
    {
        string authorization, date, apiVersion;
        Blobs(out authorization, out date, out apiVersion);

        Console.WriteLine($"Authorization: {authorization}");
        Console.WriteLine($"Date: {date}");
        Console.WriteLine($"API Version: {apiVersion}");
        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }


    static void Blobs(out string auth, out string date, out string apiversion)
    {
        string Account = "devstoreaccount1";
        string Key = "<Your-access-key>";
        string Container = "<your container name>";
        string blob = "MyFile.obj";
        apiversion = "2021-06-08";

        DateTime dt = DateTime.UtcNow;
        string StringToSign = String.Format("GET\n"
            + "\n" // content encoding
            + "\n" // content language
            + "\n" // content length
            + "\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:" + dt.ToString("R") + "\nx-ms-version:" + apiversion + "\n" // headers
            + "/{0}/{1}/{2}", Account, Container, blob);

        string signature = SignThis(StringToSign, Key, Account);

        auth = string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1}:{2}",
            "SharedKey",
            Account,
            signature);

        date = dt.ToString("R");
    }

    private static String SignThis(String StringToSign, string Key, string Account)
    {
        String signature = string.Empty;
        byte[] unicodeKey = Convert.FromBase64String(Key);
        using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
        {
            Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(StringToSign);
            signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
        }

        return signature;
    }
}

Output:

Authorization: SharedKey xxxxx:pjnrtx34Je88XqiAnq67Txxxxxxx
Date: Sat, 21 Oct 2023 05:18:57 GMT
API Version: 2021-06-08 

enter image description here

Now, In your Unity script you need to change like below:

private string DownloadString(string url)
{
    using (WebClient client = new WebClient())
    {
        client.Headers.Add("Authorization", "SharedKey devstoreaccount1:<signature>");
        client.Headers.Add("x-ms-date", "Sat, 21 Oct 2023 05:18:57 GMT");
        client.Headers.Add("x-ms-version", "2021-06-08");
        return client.DownloadString(url);
    }
}

Reference:

Call REST API operations with Shared Key authorization