Azure Blob Storage Sas Error: AuthenticationFailed - c#

I'm trying to use SAS tokens in Azure Blob Storage following this tutorial, but I hit this error:
<Error>
<Code>AuthenticationFailed</Code>
<Message>
Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:2aada4ff-901e-0011-116c-8bc84f000000 Time:2019-10-25T19:41:37.0381744Z
</Message>
<AuthenticationErrorDetail>
Signature did not match. String to sign used was r 2019-10-25T19:26:51Z 2019-10-25T20:31:51Z /blob/platinepersistencesg/$root/documents-legal-entity-01d6d631-bc1e-54e7-894e-f67297a2bae7 2019-02-02 b
</AuthenticationErrorDetail>
</Error>
Here is my code:
public async Task<IActionResult> GetSasToken()
{
const string containerName = "documents-legal-entity-01d6d631-bc1e-54e7-894e-f67297a2bae7";
const string blobName = "09578f41-e7fb-4765-bf41-869ea649f03a.pdf";
const SharedAccessBlobPermissions permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Create;
var blobClient = CloudStorageAccount
.Parse("DefaultEndpointsProtocol=https;AccountName=<account_name>;AccountKey=<account_key>;EndpointSuffix=core.windows.net")
.CreateCloudBlobClient();
var container = blobClient.GetContainerReference(containerName);
var blob = container.GetBlockBlobReference(blobName);
var policy = new SharedAccessBlobPolicy
{
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24),
Permissions = permissions
};
var sasToken = blob.GetSharedAccessSignature(policy);
var sasUri = container.Uri + sasToken;
return Ok(new { uri = sasUri });
}
I'm able to make it work following this answer, but I would like to use the Azure client instead for simplicity and to avoid carrying around the storage key.

Only thing you're missing is the blob name from your URL construction. Just need to change:
var sasUri = container.Uri + sasToken;
...to...
var sasUri = blob.Uri + sasToken;

Related

Get AccessToken for Azure Storage from InteractiveBrowserCredential

I'm trying to get an accesstoken from a InteractiveBrowserCredential so I can make calls to the Azure Datalake, but when I try to do this I get this error message:
System.Net.Http.HttpRequestException: 'Response status code does not
indicate success: 403 (Server failed to authenticate the request. Make
sure the value of Authorization header is formed correctly including
the signature.).'
Below is my code, I sspect it might be the scopes I request but I have tried every value I could find online (https://storage.azure.com/user_impersonation, https://storage.azure.com/.default).
// setup authentication
var tenantId = "common";
var clientId = "xxx";
var options = new InteractiveBrowserCredentialOptions {
TenantId = tenantId,
ClientId = clientId,
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri("http://localhost"),
};
// authenticate and request accesstoken
var interactiveCredential = new InteractiveBrowserCredential(options);
var token = interactiveCredential.GetToken(new TokenRequestContext(new[] { "https://storage.azure.com/.default" }));
// Create HttpClient
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token); ;
string res = await client.GetStringAsync("https://xx.blob.core.windows.net/?comp=list"); // this line throws the error
textBox1.Text = res;
If I use the SDK it does work, so it does not appear to be security settings.
// connect to Azure Data Lake (this works fine)
string accountName = "xxx";
string dfsUri = "https://" + accountName + ".dfs.core.windows.net";
var dataLakeServiceClient = new DataLakeServiceClient(new Uri(dfsUri), interactiveCredential);
// get filesystems
var systems = dataLakeServiceClient.GetFileSystems().ToList();// works fine
The error "403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signatureerror" usually occurs if you have not x-ms-version header in your code.
I agree with Gaurav Mantri, To resolve the error make sure to pass header value.
I tried to reproduce the same in my environment and got the same error as below in Postman:
I generated the access token by using the below Parameters:
https://login.microsoftonline.com/common/oauth2/v2.0/token
grant_type:authorization_code
client_id:4b08ee93-f4c8-47e9-bb1e-XXXXXXX
client_secret:client_secret
scope:https://storage.azure.com/user_impersonation
redirect_uri:https://jwt.ms
code: code
When I included x-ms-version=2019-02-02, I am able to access the storage account successfully like below:
The x-ms-version header value must be in the format YYYY-MM-DD.
In your code, you can include the header below samples:
Client.DefaultRequestHeaders.Add("x-ms-version", "2019-02-02");
request.Headers.Add("x-ms-version", "2019-02-02");
Reference:
Versioning for the Azure Storage services | Microsoft Learn

create a Share Account signature in c#

I would like to access a file located in azure file shares to copy it into blob storage. It works when I create Shared Access Signature by using the Microsoft Azure Storage Explorer by right-clicking on the file and going through the process and then hardcode generated value in the code. But I cannot generate this value using this C# code.
var sharedAccessFilePolicy = new SharedAccessFilePolicy()
{
Permissions = SharedAccessFilePermissions.Read,
SharedAccessStartTime = DateTime.Now,
SharedAccessExpiryTime = DateTime.Now.AddDays(1)
};
This code also generates an SAS but it does not work anyways. any idea?
BTW I'm using Microsoft.Azure.Storage NuGet
sv=2019-02-02, sr=f, sig=****, se=****, sp=r"
st=2019-11-06T10****, se=2019-11-07T10**1**, sp=rl, sv=2018-03-28, sr=f,
sig=****
the first one is generated by the code and the second one is the one that is comming from Microsoft Azure Storage Explorer
The below code I have test, it could generate the file sas token and copy the file to blob.
static async System.Threading.Tasks.Task Main(string[] args)
{
var accountName = "accountname";
var accountKey = "your account key";
var account = new CloudStorageAccount(new StorageCredentials(accountName, accountKey), true);
var fileClient = account.CreateCloudFileClient();
var share = fileClient.GetShareReference("windows");
CloudFileDirectory rootDir = share.GetRootDirectoryReference();
var file = rootDir.GetFileReference("test.json");
var sasToken = file.GetSharedAccessSignature(new Microsoft.WindowsAzure.Storage.File.SharedAccessFilePolicy()
{
Permissions = SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write,
SharedAccessExpiryTime = new DateTimeOffset(DateTime.UtcNow.AddDays(1))
});
var uri = file.StorageUri.PrimaryUri + sasToken;
Uri fileuri = new Uri(uri);
Console.WriteLine(uri);
var blobclient = account.CreateCloudBlobClient();
var containerClient =blobclient.GetContainerReference("test");
var blob=containerClient.GetBlockBlobReference("test.json");
await blob.StartCopyAsync(fileuri);
}
Hope this could help you, if you still have other problem please feel free to let me know.

Azure Blob Container, Can't Generate Token

I'm working on azure storage but I cannot create a proper SAS token to pass to my frontend javascript. Following multiple tutorials and examples, I can't seem to get a working token for JS.
I'm validating my token at on the tutorial here so that my own javascript doesn't get in my way: https://dmrelease.blob.core.windows.net/azurestoragejssample/samples/sample-blob.html
I've spent hours trying out different solutions, but my token generated looks so similar to the one generated by azure. What am I missing?
code
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
//Set the expiry time and permissions for the container.
//In this case no start time is specified, so the shared access signature becomes valid immediately.
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy();
sasConstraints.SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddHours(24);
sasConstraints.Permissions = SharedAccessBlobPermissions.List | SharedAccessBlobPermissions.Write;
//Generate the shared access signature on the container, setting the constraints directly on the signature.
string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);
//Return the URI string for the container, including the SAS token.
return sasContainerToken;
Based on my test, the code is ok for generating SAS token. If you want to list the blobs in the container, you need to add &comp=list&restype=container to your SAS URL. Then it should work.
Get https://xxxxx.blob.core.windows.net/test?sv=2018-03-28&sr=c&sig=xxxxxxxxx&sp=rwl&comp=list&restype=container
Azure Storage Service is not able to identify if the resource you're trying to access is a blob or a container and assumes it's a blob. Since it assumes the resource type is blob, it makes use of $root blob container for SAS calculation (which you can see from your error message). Since SAS was calculated for mark blob container, you get this Signature Does Not Match error. By specifying restype=container you're telling storage service to treat the resource as container. comp=list is required as per REST API specification.
For more information, please refer to another SO thread.
Regarding the issue, have you tried to use JS to create a SAS token.
var azure = require('azure-storage');
var fs = require('fs');
var SasConstants = azure.Constants.AccountSasConstants;
var blobService = azure.createBlobService();
var containerName = 'containername';
var blobName = 'blobname';
var startDate = new Date('');
var expiryDate = new Date(startDate);
expiryDate.setDate(startDate.getDate() + 1);
var sharedAccessPolicy = {
AccessPolicy: {
Permissions: azure.BlobUtilities.SharedAccessPermissions.READ + azure.BlobUtilities.SharedAccessPermissions.ADD + azure.BlobUtilities.SharedAccessPermissions.CREATE+ azure.BlobUtilities.SharedAccessPermissions.WRITE,
Start: startDate,
Expiry: expiryDate
},
};
var token = blobService.generateSharedAccessSignature(containerName, null, sharedAccessPolicy);
Generate a token for the storage account instead. The permissions in the tutorial listed are granted by the storage account policy.
public static string GenerateAccountSASToken(string connectionString)
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
SharedAccessAccountPolicy accountpolicy = new SharedAccessAccountPolicy();
accountpolicy.SharedAccessStartTime = DateTimeOffset.UtcNow.AddHours(-24);
accountpolicy.SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddHours(24);
accountpolicy.Permissions = SharedAccessAccountPermissions.Add | SharedAccessAccountPermissions.Create | SharedAccessAccountPermissions.List | SharedAccessAccountPermissions.ProcessMessages | SharedAccessAccountPermissions.Read | SharedAccessAccountPermissions.Update | SharedAccessAccountPermissions.Write;
accountpolicy.Services = SharedAccessAccountServices.Blob;
accountpolicy.ResourceTypes = SharedAccessAccountResourceTypes.Container | SharedAccessAccountResourceTypes.Object | SharedAccessAccountResourceTypes.Service;
return storageAccount.GetSharedAccessSignature(accountpolicy);
}

Can't work with Azure Blob Storage from .Net Core App

I want to use Azure Blob Storage to save some files.
For each file I have a submission date, which I also want to use to create a Container. So I can organise the files by submission date.
I can access the Blob Storage and create the Container.
But I cannot set permissions on the Container, test if a particular Blob exists within it, or add a Blob to the Container.
What am I missing?
The innerException I'm getting in every case is:
{Microsoft.WindowsAzure.Storage.StorageException: This request is not authorized to perform this operation.
at Microsoft.WindowsAzure.Storage.Core.Executor.Executor.<ExecuteAsyncInternal>d__4`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task)
at Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer.<>c__DisplayClass64_0.<<SetPermissionsAsync>b__0>d.MoveNext()
Request Information
RequestID:XXXXXX
RequestDate:XXXXXX
StatusMessage:This request is not authorized to perform this operation.
ErrorCode:AuthorizationFailure
ErrorMessage:This request is not authorized to perform this operation.
RequestId:XXXXXX
Time:XXXXXX
}
Obviously an AuthorizationFailure as it says. But I can access the Blob Storage and create a Container. So what gives?
Here's the complete .Net Core 2 test program (minus a little redaction)
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
namespace AzureStorageTestApp
{
public class Program
{
public static void Main(string[] args)
{
var storageAccount = GetStorageAccount();
var policy = new SharedAccessAccountPolicy()
{
Permissions = SharedAccessAccountPermissions.Read | SharedAccessAccountPermissions.Write | SharedAccessAccountPermissions.List,
Services = SharedAccessAccountServices.Blob | SharedAccessAccountServices.File,
ResourceTypes = SharedAccessAccountResourceTypes.Service | SharedAccessAccountResourceTypes.Container,
SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-15),
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24),
Protocols = SharedAccessProtocol.HttpsOnly
};
var sasToken = storageAccount.GetSharedAccessSignature(policy);
var creds = new StorageCredentials(sasToken);
var accountWithSAS = new CloudStorageAccount(creds, storageAccount.Credentials.AccountName, null, true);
var blobClient = accountWithSAS.CreateCloudBlobClient();
var containerName = DateTime.UtcNow.ToString("yyyy-MM-dd");
var container = blobClient.GetContainerReference(containerName);
if (container.CreateIfNotExistsAsync().Result)
{
var perms = new BlobContainerPermissions
{
PublicAccess = BlobContainerPublicAccessType.Blob
};
// Blows up here
container.SetPermissionsAsync(perms).Wait();
}
var fileName = "testfile.txt";
var fileData = "some file data";
var blob = container.GetBlockBlobReference(fileName);
bool shouldUpload = true;
// Blows up here
var blobExists = blob.ExistsAsync().Result;
if (blobExists)
{
blob.FetchAttributesAsync().Wait();
if (blob.Properties.Length == fileData.Length)
{
shouldUpload = false;
}
}
if (shouldUpload)
{
// Blows up here
blob.UploadTextAsync(fileData).Wait();
}
}
private static CloudStorageAccount GetStorageAccount()
{
var connectionString = "DefaultEndpointsProtocol=https;AccountName=<Redacted>;AccountKey=<Redacted>;EndpointSuffix=core.windows.net";
return CloudStorageAccount.Parse(connectionString);
}
}
}
The same story - it just doesn't work (fails with auth error).
Use CloudStorageAccount.Parse with BLOB connection string:
CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=***;AccountKey=***;EndpointSuffix=core.windows.net");
OK so here's "the answer".
If you're using a CloudStorageAccount set up with a SharedAccessAccountPolicy (instead of actual credentials) then that CloudStorageAccount can only be used for storage account management itself.
So for instance you can create a Blob Container, but you cannot set the permissions on that container, for that you'll need an appropriately created SharedAccessBlobPolicy or actual credentials.
So far my answer is to throw away all the stuff I had set up to use the SharedAccessAccountPolicy, ending up with the following code at the start:
var storageAccount = GetStorageAccount();
var blobClient = storageAccount.CreateCloudBlobClient();

Programmatically get Azure storage account properties

I wrote in my C# web application a method that deletes old blobs from Azure storage account.
This is my code:
public void CleanupIotHubExpiredBlobs()
{
const string StorageAccountName = "storageName";
const string StorageAccountKey = "XXXXXXXXXX";
const string StorageContainerName = "outputblob";
string storageConnectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", StorageAccountName, StorageAccountKey);
// Retrieve storage account from connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageConnectionString);
// Create the blob client.
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
// select container in which to look for old blobs.
CloudBlobContainer container = blobClient.GetContainerReference(StorageContainerName);
// set up Blob access condition option which will filter all the blobs which are not modified for X (this.m_CleanupExpirationNumOfDays) amount of days
IEnumerable<IListBlobItem> blobs = container.ListBlobs("", true);
foreach (IListBlobItem blob in blobs)
{
CloudBlockBlob cloudBlob = blob as CloudBlockBlob;
Console.WriteLine(cloudBlob.Properties);
cloudBlob.DeleteIfExists(DeleteSnapshotsOption.None, AccessCondition.GenerateIfNotModifiedSinceCondition(DateTime.Now.AddDays(-1 * 0.04)), null, null);
}
LogMessageToFile("Remove old blobs from storage account");
}
as you can see, In order to achieve that The method has to receive StorageAccountName and StorageAccountKey parameters.
One way to do that is by configuring these parameters in a config file for the app to use, But this means the user has to manually insert these two parameters to the config file.
My question is:
is there a way to programmatically retrieve at least one of these parameters in my code, so that at least the user will have to insert only one parameters and not two? my goal is to make the user's life easier.
My question is: is there a way to programmatically retrieve at least one of these parameters in my code, so that at least the user will have to insert only one parameters and not two? my goal is to make the user's life easier.
According to your description, I suggest you could use azure rest api to get the storage account key by using account name.
Besides, we could also use rest api to list all the rescourse group's storage account name, but it still need to send the rescourse group name as parameter to the azure management url.
You could send the request to the azure management as below url:
POST: https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resrouceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}/listKeys?api-version=2016-01-01
Authorization: Bearer {token}
More details, you could refer to below codes:
Notice: Using this way, you need firstly create an Azure Active Directory application and service principal. After you generate the service principal, you could get the applicationid,access key and talentid. More details, you could refer to this article.
Code:
string tenantId = " ";
string clientId = " ";
string clientSecret = " ";
string subscription = " ";
string resourcegroup = "BrandoSecondTest";
string accountname = "brandofirststorage";
string authContextURL = "https://login.windows.net/" + tenantId;
var authenticationContext = new AuthenticationContext(authContextURL);
var credential = new ClientCredential(clientId, clientSecret);
var result = authenticationContext.AcquireTokenAsync(resource: "https://management.azure.com/", clientCredential: credential).Result;
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Storage/storageAccounts/{2}/listKeys?api-version=2016-01-01", subscription, resourcegroup, accountname));
request.Method = "POST";
request.Headers["Authorization"] = "Bearer " + token;
request.ContentType = "application/json";
request.ContentLength = 0;
//Get the response
var httpResponse = (HttpWebResponse)request.GetResponse();
using (System.IO.StreamReader r = new System.IO.StreamReader(httpResponse.GetResponseStream()))
{
string jsonResponse = r.ReadToEnd();
Console.WriteLine(jsonResponse);
}
Result:

Categories