I am uploading files from Cordova to Azure Blob Storage through a C# API that I created. Recently, the upload stopped working. I had been making changes, so I reverted my changes but the issue is still occurring. I've debugged the process as best I can but I seem to be stuck.
The Process
When I want to upload a file, these steps are taken:
- Data about the file is uploaded to Azure SQL. During the insert, a SAS key is generated and returned.
- The file is uploaded to the C# Web API via JavaScript and the SAS key is sent along with it.
- The Web API stores the file locally and then calls the WebClient.UploadFile method using the SAS key for authentication.
- The Web API should return a 200.
The Error
The first part of the process works well. I generate a sas key properly (with a five minute window - I've expanded it to 15 without success) and the JavaScript upload of the file to C# Web API goes well (I've put a breakpoint in the code and verified everything). The issue arises when I call the WebClient.UploadFile method. It used to work without issue. Now I get a 403 error. The status property of the error says ProtocolError. When I run it in Fiddler, The response I get back is:
Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
Signature fields not well formed.
The Request
Here is the raw view of the PUT call to my Azure storage account (names changed to protect the innocent):
PUT
HTTP/1.1 x-ms-blob-type: BlockBlob x-ms-date: Tue, 11 Nov 2014
19:17:24 GMT x-ms-version: 2012-02-12 Content-Type:
application/octet-stream Host: myappname.blob.core.windows.net
Content-Length: 74784 Expect: 100-continue
And here is the SAS key that I generated:
se=2014-11-11T19%3A32%3A33Z&sr=b&sp=w&sig=Lk5SiEEaGUwyOjxjUGkIjink26Gwe7VpXMBPw975ZrM%3D
The Code
Here is my C# Code:
var client = new WebClient();
client.Headers.Add("x-ms-blob-type", "BlockBlob");
client.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R",
System.Globalization.CultureInfo.InvariantCulture));
client.Headers.Add("x-ms-version", "2012-02-12");
sas = Request.RequestUri.Query.Substring(Request.RequestUri.Query.IndexOf("sas=") + 4);
startIndex = Request.RequestUri.Query.IndexOf("name=") + 5;
endIndex = Request.RequestUri.Query.IndexOf("&", startIndex);
fileName = Request.RequestUri.Query.Substring(startIndex, endIndex - startIndex);
client.UploadFile(string.Format(@"{0}/{1}?{2}", endpoint, fileName, sas), "PUT", file.LocalFileName);
And here is my JavaScript code that runs on the Insert call of to the SQL Azure table via Mobile Services:
var azure = require('azure');
var qs = require('querystring');
var appSettings = require('mobileservice-config').appSettings;
function insert(item, user, request) {
// Get storage account settings from app settings.
var accountName = appSettings.STORAGE_ACCOUNT_NAME;
var accountKey = appSettings.STORAGE_ACCOUNT_ACCESS_KEY;
var host = accountName + '.blob.core.windows.net';
if ((typeof item.containerName !== "undefined") && (
item.containerName !== null)) {
// Set the BLOB store container name on the item, which must be lowercase.
item.containerName = item.containerName.toLowerCase();
// If it does not already exist, create the container
// with public read access for blobs.
var blobService = azure.createBlobService(accountName, accountKey, host);
blobService.createContainerIfNotExists(item.containerName, {
publicAccessLevel: 'blob'
}, function(error) {
if (!error) {
// Provide write access to the container for the next 5 mins.
var sharedAccessPolicy = {
AccessPolicy: {
Permissions: azure.Constants.BlobConstants.SharedAccessPermissions.WRITE,
Expiry: new Date(new Date().getTime() + 5 * 60 * 1000)
}
};
// Generate the upload URL with SAS for the new image.
var sasQueryUrl =
blobService.generateSharedAccessSignature(item.containerName,
item.resourceName, sharedAccessPolicy);
// Set the query string.
item.sasQueryString = qs.stringify(sasQueryUrl.queryString);
// Set the full path on the new new item,
// which is used for data binding on the client.
item.imagePath = sasQueryUrl.baseUrl + sasQueryUrl.path;
} else {
console.error(error);
}
request.execute();
});
} else {
request.execute();
}
}
Like I said, on the C#/Azure side, nothing has changed to my knowledge. I had been playing around with the file upload on the Cordova side, but since the file gets successfully to the C# Web API, I can't see how that would be an issue.
I am getting errors in my Azure Storage logs, but I'm not sure if this is truly an error or not. The error I am getting is a 409 on the CreateContainer action. The message is ClientOtherError. The reason I'm not sure this is related or not is because of the createContainerIfNotExists method. I'm not sure if that would generate an error if the container already exists (doesn't seem like it should but you never know).
%
sign is getting encoded as%25
which should not be happening. Your request URL should end with%3D
which is URL encoding for=
. Can you please check why this is happening? – Gaurav Mantri