Can't delete Azure Storage Table using Azure REST API - azure

I am getting an error "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature."
I followed the authorization tutorial provided by Microsoft, Delete Table, Authentication for the Azure Storage Services.
Am I missing anything?

It seems that you’d like to delete table via rest api.
DELETE https://myaccount.table.core.windows.net/Tables('mytable')
the following sample works fine on my side, please refer to the code to generate the signature.
string StorageAccount = "account name here";
string StorageKey = "account key here";
string tablename = "table name";
string requestMethod = "DELETE";
string mxdate = "";
string storageServiceVersion = "2015-12-11";
protected void Button1_Click(object sender, EventArgs e)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(string.Format(CultureInfo.InvariantCulture,
"https://{0}.table.core.windows.net/Tables('{1}')",
StorageAccount, tablename));
req.Method = requestMethod;
//specify request header
string AuthorizationHeader = generateAuthorizationHeader();
req.Headers.Add("Authorization", AuthorizationHeader);
req.Headers.Add("x-ms-date", mxdate);
req.Headers.Add("x-ms-version", storageServiceVersion);
req.ContentType = "application/json";
req.Accept = "application/json;odata=minimalmetadata";
using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
{
}
}
public string generateAuthorizationHeader()
{
mxdate = DateTime.UtcNow.ToString("R");
string canonicalizedResource = $"/{StorageAccount}/Tables('{tablename}')";
string contentType = "application/json";
string stringToSign = $"{requestMethod}\n\n{contentType}\n{mxdate}\n{canonicalizedResource}";
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(StorageKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
String authorization = String.Format("{0} {1}:{2}",
"SharedKey",
StorageAccount,
signature
);
return authorization;
}

Related

Is it possible to get access / refresh tokens from Azure Active Directory (AAD) without logging in through browser?

For an email account of my organization, we must perform some post-processing of the emails that arrive at the inbox through a cron. Is it possible to perform the authentication flow from a cron without having to enter the credentials in the browser?
What kind of application should it be (single-tenant, multi-tenant, etc.)? What kind of permissions should it have? What authentication flow should I use (client credentials, authorization code, etc)? Can it be done through Python's MSAL?
Thank you in advance!
Based on the SO reference,
We do have a code to getAccessToken() that successfully returns an access token
private String getUserNamesFromGraph() throws Exception {
String bearerToken = "Bearer "+getAccessToken();
String url = "https://graph.microsoft.com/v1.0/users";
String returnData = null;
try {
URL apiURL = new URL(url);
URLConnection con = apiURL.openConnection();
con.setRequestProperty("Authorization", bearerToken);
con.setRequestProperty("Content-Type", "application/json");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
returnData = response.toString();
System.out.println(returnData);
} catch(Exception e) {
System.out.println(e);
}
return returnData;
}
private String getAccessToken() throws Exception {
String url = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
// header
con.setRequestMethod("POST");
con.setRequestProperty("User-Agent", "eTarget API");
con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
String urlParameters = "client_id=***
APPLICATION ID FROM APPLICATION REGISTRATION PORTAL ***&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=***
APPLICATION SECRET FROM APPLICATION REGISTRATION PORTAL ***&grant_type=client_credentials";
// Send post request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
int responseCode = con.getResponseCode();
System.out.println("\nSending 'POST' request to URL : " + url);
System.out.println("Post parameters : " + urlParameters);
System.out.println("Response Code : " + responseCode);
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
//print result
String returnData = response.toString();
System.out.println(returnData);
Map jsonTokenData = new Gson().fromJson(returnData, Map.class);
String accessToken = (String)jsonTokenData.get("access_token");
//System.out.println(accessToken);
return accessToken;
}
Permissions:
The application to read the users it has to have an explicitly granted User.Read.All application permission along with admin consent.
A daemon application without user interaction, you have to look at the application permissions and not delegated permissions.
Post all changes, you may use https://jwt.ms to check your access token
SO Reference: java - Microsoft Graph 401 Unauthorized with access token - Stack Overflow

Azure Blob Storage - Error trying to delete blob with API REST with C#

I'm working with Azure Blob Storage and C#. I need to delete a blob using the API REST, but I get the 403 Forbidden error. I'm using the same function to generate the authentication header for create a new blob, but it works for put but doesn't it for delete. This is my code:
public bool DeleteBlob(string containerName, string fileName)
{
string blobName = fileName;
string method = "DELETE";
string requestUri = $"https://{_accountName}.blob.core.windows.net/{containerName}/{blobName}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
string now = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
request.Method = method;
request.Headers.Add("x-ms-version", "2020-10-02");
request.Headers.Add("x-ms-date", now);
request.Headers.Add("x-ms-delete-snapshots", "include");
request.Headers.Add("Authorization", AuthorizationHeader("DELETE", request, containerName, blobName));
bool result = false;
using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
{
if (resp.StatusCode != HttpStatusCode.Accepted && resp.StatusCode != HttpStatusCode.OK)
{
throw new Exception(resp.StatusDescription);
}
result = true;
}
return result;
}
private string AuthorizationHeader(string method, HttpWebRequest request, string containerName, string blobName)
{
string urlResource = $"/{_accountName}/{containerName}/{blobName}";
string stringToSign = $"{method}\n\n\n{request.ContentLength}\n\n{request.ContentType}\n\n\n\n\n\n\n{GetCanonicalizedHeaders(request)}{GetCanonicalizedResource(request.RequestUri, _accountName)}";
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(_accountKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
return string.Format("{0} {1}:{2}", "SharedKey", _accountName, signature);
}
private string GetAuthorizationHeader(string method, HttpWebRequest request, string now, string containerName, string blobName)
{
string urlResource = $"/{_accountName}/{containerName}/{blobName}";
var canonicalizedStringToBuild = string.Format("{0}\n{1}", now, $"/{_accountName}/" + urlResource);
string signature;
using (var hmac = new HMACSHA256(Convert.FromBase64String(_accountKey)))
{
byte[] dataToHmac = Encoding.UTF8.GetBytes(canonicalizedStringToBuild);
signature = Convert.ToBase64String(hmac.ComputeHash(dataToHmac));
}
return string.Format($"{_accountName}:" + signature);
}
private string GetCanonicalizedHeaders(HttpWebRequest request)
{
ArrayList headerNameList = new ArrayList();
StringBuilder sb = new StringBuilder();
foreach (string headerName in request.Headers.Keys)
{
if (headerName.ToLowerInvariant().StartsWith("x-ms-", StringComparison.Ordinal))
{
headerNameList.Add(headerName.ToLowerInvariant());
}
}
headerNameList.Sort();
foreach (string headerName in headerNameList)
{
StringBuilder builder = new StringBuilder(headerName);
string separator = ":";
foreach (string headerValue in GetHeaderValues(request.Headers, headerName))
{
string trimmedValue = headerValue.Replace("\r\n", String.Empty);
builder.Append(separator);
builder.Append(trimmedValue);
separator = ",";
}
sb.Append(builder.ToString());
sb.Append("\n");
}
return sb.ToString();
}
private string GetCanonicalizedResource(Uri address, string accountName)
{
StringBuilder str = new StringBuilder();
StringBuilder builder = new StringBuilder("/");
builder.Append(accountName);
builder.Append(address.AbsolutePath);
str.Append(builder.ToString());
NameValueCollection values2 = new NameValueCollection();
if (address.Query.Length > 0)
{
string query = address.Query.Remove(0, 1);
var queryParts = query.Split('&');
foreach (var queryPart in queryParts)
{
var parts = queryPart.Split('=');
values2.Add(parts[0], parts[1]);
}
ArrayList list2 = new ArrayList(values2.AllKeys);
list2.Sort();
foreach (string str3 in list2)
{
StringBuilder builder3 = new StringBuilder(string.Empty);
builder3.Append(str3);
builder3.Append(":");
builder3.Append(values2[str3]);
str.Append("\n");
str.Append(builder3.ToString());
}
}
return str.ToString();
}
Could anyone tell me what I'm doing wrong? As I commented for create a new blob working fine, but not for deletion. I don't know if the authentication header is different for delete (I know the method name is different) I have not found much information about that.
Thannks.
The reason your delete operation is failing with 403 error is because in your stringToSign you're assigning content length as 0 whereas it should be an empty string.
Please change your AuthorizationHeader method code to the code mentioned below and your delete blob operation should work just fine:
private string AuthorizationHeader(string method, HttpWebRequest request, string containerName, string blobName)
{
string urlResource = $"/{_accountName}/{containerName}/{blobName}";
var contentLength = request.ContentLength == 0 ? "" : request.ContentLength.ToString();
string stringToSign = $"{method}\n\n\n{contentLength}\n\n{request.ContentType}\n\n\n\n\n\n\n{GetCanonicalizedHeaders(request)}{GetCanonicalizedResource(request.RequestUri, _accountName)}";
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(_accountKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
return string.Format("{0} {1}:{2}", "SharedKey", _accountName, signature);
}
What I have done above is set the content length value to an empty string if the content length is 0.
From this link (emphasis mine):
In the current version, the Content-Length field must be an empty
string if the content length of the request is zero. In version
2014-02-14 and earlier, the content length was included even if zero.

Problems with generating SAS manually for blob file

I'm trying to make a downloader attached to the service bus that is going to download the files from blob storage. But I'm having some problems with generating the SAS token manually, please se the error message below.
I'm getting error <AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail>
private static string createToken(string resourceUri, string keyName, string key)
{
var now = DateTime.UtcNow;
TimeSpan sinceEpoch = now - new DateTime(1970, 1, 1);
var time = 60 * 2;
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + time);
var expiryDateString = now.AddSeconds(time).ToUniversalTime().ToString("u");
string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = String.Format(
CultureInfo.InvariantCulture,
"{0}?sp={1}&st={2}&se={3}&spr={4}&sv={5}&sr={6}&sig={7}",
resourceUri,
"r",
DateTime.UtcNow.ToUniversalTime().ToString("u"),
expiryDateString,
"https",
"2019-02-02",
"b",
HttpUtility.UrlEncode(signature));
return sasToken;
}
Is it the stringToSign? Or signature as a whole? I'm a but unusre, to maybe I need to use the HttpUtility.UrlEncoder on all fields?
Please try the code below, it generates a working sas token for me:
static void Main(string[] args)
{
string resourceUri = "https://xx.blob.core.windows.net/test1/1.txt";
string account_name= "storage account name";
string key = "storage account key";
string s1 = createToken(resourceUri,account_name,key);
Console.WriteLine(s1);
Console.ReadLine();
}
private static string createToken(string resourceUri, string account_name, string key)
{
var accountName = account_name;
var accountKey = key;
var start = DateTime.UtcNow.AddHours(-2).ToString("yyyy-MM-ddTHH:mm:ssZ");
var end = DateTime.UtcNow.AddHours(2).ToString("yyyy-MM-ddTHH:mm:ssZ");
var permission = "rwdlac";
var serviceType = "b";
var resourceTypes = "sco";
var protocol = "https";
var serviceVersion = "2019-02-02";
//here is the difference from your code.
var stringToSign = string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n",accountName, permission,serviceType,resourceTypes,start,end,"",protocol,serviceVersion);
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(accountKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = string.Format("?sv={0}&ss={1}&srt={2}&sp={3}&se={4}&st={5}&spr={6}&sig={7}", serviceVersion,
serviceType, resourceTypes, permission, end, start, protocol, HttpUtility.UrlEncode(signature));
var urlToListTables = resourceUri + sasToken;
return urlToListTables;
}
Please let me know if you still have any issues.

How to programatically create and get Web Apps under App Services in Windows Azure?

I would like to programatically get and create Web Apps under App Services in Windows Azure.
How to create it by using its REST API?
As far as I know, if you want to use REST API to get and create Web App, you need generate a access token. To generate access token we need talentID, clientId, clientSecret. We need to assign application to role, after that then we can use token in common. More information about how to assign application to role please refer to the article .This blog also has more detail steps to get AceessToken.
Notice: I use C# Codes to show a example.
Then you could send request as below:
Get webapp:
Url: https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/Microsoft.Web/sites?api-version={api-version}
Method: PUT
Parameter:
subscriptionId The identifier of your subscription where the snapshot is being created.
resourceGroup The name of the resource group that will contain the snapshot.
api-version The version of the API to use.
The codes is as below:
// Display the file contents to the console. Variable text is a string.
string tenantId = "xxxxxxxxxxxxxxxxxxxxx";
string clientId = "xxxxxxxxxxxxxxxxxxxxx";
string clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
string subscriptionid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
string resourcegroup = "BrandoSecondTest";
string version = "2015-08-01";
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.Web/sites?api-version={2}", subscriptionid, resourcegroup, version));
request.Method = "GET";
request.Headers["Authorization"] = "Bearer " + token;
request.ContentType = "application/json";
var httpResponse = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
Console.WriteLine(streamReader.ReadToEnd());
}
Console.ReadLine();
Create WebApp:
You should send your webapp template in the request content.
Then you could send request as below:
Url: https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/Microsoft.Web/sites/{appname}?api-version={api-version}
Method: PUT
Parameter:
subscriptionId The identifier of your subscription where the snapshot is being created.
resourceGroup The name of the resource group that will contain the snapshot.
appname: The name of your webapplicaiton you want to create
api-version The version of the API to use.
Request content:
{
"location": "East Asia",
"properties": {
"name": "YourWebpplcaitonName",
"serverFarmId": "Yourserviceplan"
}
}
Here is a C# example, hope it gives some tips:
string body = File.ReadAllText(#"D:\json.txt");
// Display the file contents to the console. Variable text is a string.
string tenantId = "xxxxxxxxxxxxxxxxxxxxxxxxx";
string clientId = "xxxxxxxxxxxxxxxxxxxxxxx";
string clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxx";
string subscriptionid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
string resourcegroup = "BrandoSecondTest";
string appname = "Brandosss";
string version = "2015-08-01";
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.Web/sites/{2}?api-version={3}", subscriptionid, resourcegroup,appname, version));
request.Method = "PUT";
request.Headers["Authorization"] = "Bearer " + token;
request.ContentLength = body.Length;
request.ContentType = "application/json";
try
{
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(body);
streamWriter.Flush();
streamWriter.Close();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
// Get the response
var httpResponse = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
Console.WriteLine(streamReader.ReadToEnd());
}

getting 403 forbidden webexception for azure blob put request

I am using this code to upload some text directly to azure blob using rest api. I am getting an webexception 403 forbidden. Can someone plaese tell me where am i going wrong in my code
private String CreateAuthorizationHeader(String canonicalizedString, CloudBlob blob)
{
String signature = string.Empty;
using (HMACSHA256 hmacSha256 = new HMACSHA256())
{
Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(canonicalizedString);
signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
}
String authorizationHeader = String.Format(CultureInfo.InvariantCulture, "{0} {1}:{2}", "SharedKeyLite", myaccount, signature);
return authorizationHeader;
}
private void PutBlob(String containerName, String blobName , CloudBlob blob)
{
String requestMethod = "PUT";
String urlPath = String.Format("{0}", blobName);
String storageServiceVersion = "2009-10-01";
String dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
// if (uploadPST.HasFile)
// {
string content = "Sample file";
// Stream content = uploadPST.FileBytes;
UTF8Encoding utf8Encoding = new UTF8Encoding();
Byte[] blobContent = utf8Encoding.GetBytes(content);
Int32 blobLength = blobContent.Length;
const String blobType = "BlockBlob";
/* String canonicalizedHeaders = String.Format(
"x-ms-blob-type:{0}\nx-ms-date:{1}\nx-ms-version:{2}",
blobType,
dateInRfc1123Format,
storageServiceVersion);*/
String canonicalizedHeaders = String.Format(
"x-ms-date:{0}\nx-ms-meta-m1:{1}\nx-ms-meta-m1:{2}",
dateInRfc1123Format,
"v1",
"v2");
String canonicalizedResource = String.Format("/{0}/{1}", myaccount, urlPath);
String stringToSign = String.Format(
"{0}\n\n{1}\n\n{2}\n{3}",
requestMethod,
"text/plain; charset=UTF-8",
canonicalizedHeaders,
canonicalizedResource);
String authorizationHeader = CreateAuthorizationHeader(stringToSign, blob);
Uri uri = new Uri(CloudStorageAccount.FromConfigurationSetting("DataConnectionString").BlobEndpoint + "/" + urlPath);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = requestMethod;
// request.Headers.Add("x-ms-blob-type", blobType);
request.Headers.Add("x-ms-date", dateInRfc1123Format);
// request.Headers.Add("x-ms-version", storageServiceVersion);
request.Headers.Add("Authorization", authorizationHeader);
request.ContentLength = blobLength;
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(blobContent ,0 ,blobLength);
}
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
String ETag = response.Headers["ETag"];
}
// }
}
First, you construct an HMACSHA256 object using the default constructor -- this causes a random key to be generated and used for signing. What you want is the overload which accepts a string - and pass the azure account key.
Still, signing the request 'manually' can be tricky, as there are a lot of things to do and it's easy to mess up or forget something. Instead, I recommend you use the SignRequest method of StorageCredentialsAccountAndKey class (msdn doc) For instance;
// ...there exists a request object, and strings for the account name and key
var creds = new StorageCredentialsAccountAndKey(accountName, accountKey);
creds.SignRequest(request);
This will do everything needed to sign the request properly, including creating a canonicalized headers string, creating a date with the correct format, etc.

Resources