Reputation: 1075
tl;dr: I'm trying to connect to an Azure BlobStorage from C# using a SAS, but cannot seem to get past a StorageException
saying the "Server failed to authenticate the request." I suspect something is wrong with my connection string.
I am using libraries Microsoft.Azure.Storage.Blob
11.2.3.0 and Microsoft.Azure.Storage.Common
11.2.3.0 to connect to an Azure BlobStorage from a .NET Core 3.1 application.
My C# code to establish this connection starts as follows:
var storageAccount = CloudStorageAccount.Parse(connectionString);
var blobClient = storageAccount.CreateCloudBlobClient();
When using a connection string that includes the fields AccountName
, AccountKey
, EndpointSuffix
, and DefaultEndpointsProtocol
(this is https
)1, this works flawlessly. (I can connect and do things like enumerate containers and blobs, and also create containers and blobs and upload data.)
Now, I'm supposed to connect to that same BlobStorage with a SAS. For that purpose, I have been given the following bits of information (here anonymized to some kind of "placeholders" because I am going to refer to them in the following):
<StorageAccount>
(same as the value for the AccountName
field in the aforementioned connection string)<Container>
<Blob-SAS-Token>
<Blob-SAS-URL>
(this is actually composed of the other info found here, i.e. <DefaultEndpointsProtocol>://<StorageAccount>.blob.<EndpointSuffix>/<Container>?<Blob-SAS-Token>
)I can successfully establish a connection to said BlobStorage container from Microsoft Azure Storage Explorer by using the <Blob-SAS-URL>
as described above.2
However, I have so far been unable to successfully use the SAS from my C# code.
I have tried various ways to write my connection string, such as:
BlobEndpoint=<DefaultEndpointsProtocol>://<StorageAccount>.blob.<EndpointSuffix>;SharedAccessSignature=<Blob-SAS-Token>
BlobEndpoint=<DefaultEndpointsProtocol>://<StorageAccount>.blob.<EndpointSuffix>/<Container>;SharedAccessSignature=<Blob-SAS-Token>
According to the docs, this should work. In there, a SAS-based connection string is outlined to contain up to five fields - BlobEndpoint
, QueueEndpoint
, TableEndpoint
, FileEndpoint
, and SharedAccessSignature
- where only (any) one of the four endpoints must be specified beside the SAS.
I have also tried creating/populating the CloudStorageAccount
instance differently than by means of a (possibly misformatted) connection string, such as:
var storageAccount = new CloudStorageAccount(
new StorageCredentials("<Blob-SAS-Token>"),
"<StorageAccount>", "<EndpointSuffix>", true);
Unfortunately, no matter what I try, as soon as I attempt to retrieve any data from the BlobStorage this way, a Microsoft.Azure.Storage.StorageException
is thrown, stating
Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
UTC time on my local machine exactly matches current UTC time I can find on online sources. As I can connect with my SAS information from another application on the very same machine, the issue is most probably not related to any aspect of the environment (such as a missing entry for my IP address in some blocking list or similar), anyway. Rather than that, I suspect I am not providing an adequate set of information out of what I have been given for establishing the connection.
What do I have to change to make this connection work?
1: I am going to write this post with some sort of "placeholders" for the various identifiers and credentials so as to not divulge any confidential information. I hope this is not too confusing. On the other hand, it should actually allow for easily adapting the information to whichever sample endpoint is available by a couple of find-and-replace operations.
2: To do so, I right-click the Storage Accounts tree node, then Connect to Azure Storage... > Blob Container > SAS URL (Shared Access Signature
Upvotes: 1
Views: 6353
Reputation: 1075
Connection string-based solution below.
Based on Swetha's comment, I was able to connect using the following code:
var creds = new StorageCredentials("<Blob-SAS-Token>");
var blobContainer = new CloudBlobContainer(new Uri("<DefaultEndpointsProtocol>://<StorageAccount>.blob.<EndpointSuffix>/<Container>"), creds);
I can then retrieve and upload data from/to blobContainer
.
Unfortunately, this means the code for connecting to the BlobStorage looks differently than with a connection string. Therefore, I will need to provide, test, and maintain two different code paths, depending on which kind of connection configuration has been supplied (and there is no guarantee a third authentication method will not require yet another code path - something I have now asked about in a separate question).
UPDATE: Based upon an answer to my question about storing connection data, I have now learned that it is actually possible to connect with a SAS using a connection string. The following formats have proven valid:
BlobEndpoint=<DefaultEndpointsProtocol>://<StorageAccount>.blob.<EndpointSuffix>;SharedAccessSignature=<Blob-SAS-Token>
DefaultEndpointsProtocol=<DefaultEndpointsProtocol>;EndpointsSuffix=<EndpointsSuffix>;SharedAccessSignature=<Blob-SAS-Token>
Then, an important point is that (at least if the SAS is container-specific like the one I got - I'm not sure this is always the case) I can work in the container, but not on the container (or anything outside of it). That is:
CloudBlobContainer
instance by passing the container name to GetContainerReference
, I cannot call ListContainers
(this throws an exception rather than enumerating just the set of containers that I can access with the SAS).Exists()
and CreateIfNotExists()
will throw exceptions when called on the container.1Once sticking to these restrictions, it is possible to use CloudStorageAccount.Parse(connectionString)
again.
1: I have created a separate question about how to safely check for a container's existence.
Upvotes: 3