I'm trying to make a POST request to an API, and have successfully done this in Postman. Now, because Postman offers code snippets, I can just copy-paste this to a C# application and get the exact same result.
Here's the problem: The auth expires. If I make a call in Postman and copy the nonce, signature, and timestamp values into my application and use it that way, it works fine for a few minutes. Eventually it expires and the call doesn't work, it returns a 0005. Therefore, I have to generate the signature, nonce, and timestamp values myself, in my application.
I already spent so many hours trying to do this but I can't figure out why it's not working. I tried several ways, but the one that seems to have gotten me the furthest is the below:
The following code is given by Postman after successfully making the call (using RestSharp):
var client = new RestClient("api-url.com");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "OAuth oauth_consumer_key=\"xxxx5004bbf7xxxxxx3d3e15633f01xx\",oauth_token=\"xxx894c7c76c4axxx93d833804615xxx\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1605587985\",oauth_nonce=\"TNTCB73am0T\",oauth_version=\"1.0\",oauth_signature=\"6P7uvtxxxsEv1pz6JfxxxxlEmDY%3D\"");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\r\n \"start\": {\"from\": 1604542428000, \"to\": 1605147228000},\r\n \"status\": [\"CLOSE\"]\r\n}\r\n", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
Then, I use the following to get the Base64 of the signature string
public static string GetSignatureBaseString(string TimeStamp, string Nonce)
{
//1.Convert the HTTP Method to uppercase and set the output string equal to this value.
string Signature_Base_String = "Get";
Signature_Base_String = Signature_Base_String.ToUpper();
//2.Append the ‘&’ character to the output string.
Signature_Base_String = Signature_Base_String + "&";
//3.Percent encode the URL and append it to the output string.
string PercentEncodedURL = Uri.EscapeDataString(GetAppleApiUrl.GetUrl(AppleApiUrl.SESSION_TOKEN));
Signature_Base_String = Signature_Base_String + PercentEncodedURL;
//4.Append the ‘&’ character to the output string.
Signature_Base_String = Signature_Base_String + "&";
//5.append parameter string to the output string.
Signature_Base_String = Signature_Base_String + Uri.EscapeDataString("oauth_consumer_key=" + Settings.SettingsManager.consumer_key);
Signature_Base_String = Signature_Base_String + Uri.EscapeDataString("&oauth_token=" + Settings.SettingsManager.access_token);
Signature_Base_String = Signature_Base_String + Uri.EscapeDataString("&oauth_signature_method=" +"HMAC-SHA1");
Signature_Base_String = Signature_Base_String + Uri.EscapeDataString("&oauth_timestamp=" + TimeStamp);
Signature_Base_String = Signature_Base_String + Uri.EscapeDataString("&oauth_nonce=" + Nonce);
Signature_Base_String = Signature_Base_String + Uri.EscapeDataString("&oauth_version=" + "1.0");
return Signature_Base_String;
}
Finally, to get the sha1 hash
public static string GetSha1Hash(string key, string message)
{
var encoding = new System.Text.ASCIIEncoding();
byte[] keyBytes = encoding.GetBytes(key);
byte[] messageBytes = encoding.GetBytes(message);
string Sha1Result = string.Empty;
using (HMACSHA1 SHA1 = new HMACSHA1(keyBytes))
{
var Hashed = SHA1.ComputeHash(messageBytes);
Sha1Result = Convert.ToBase64String(Hashed);
}
return Sha1Result;
}
The OAuth 1.0 string it outputs and sends to the server is as follows:
OAuth oauth_consumer_key="c3e75004bbfxxxxxxx5f0104",oauth_token="e90xxxxxx38046158d3",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1605588743",oauth_nonce="MTYwNTU4ODc0Mw%3D%3D",oauth_version="1.0",oauth_signature="4jKad%2xxxxxxSV%xxxxxxxxxxxMP%2FI%3D"
The way I combine this is simply to replace the keys in the successful Postman call with a string format {0} {1} {2} etc with the values generated by the two functions: key & access token is fine, timestamp I get from the current time obv, nonce is the base64 of the timestamp, and then the signature is the hash on the base64 signature from funct1 using secret & tokensecret.
Which looks a lot like the Postman once (except that their nonce is always very short compared to mine but I assume that's not related to the problem).
Can anyone spot what I'm doing wrong?
Edit: Extra code
The way I use all the above:
var timeStamp = (System.Convert.ToInt32((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds)).ToString();
var nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(timeStamp)).ToString();
SortedDictionary<string, string> @params = new SortedDictionary<string, string>();
@params.Add("application/json", "{" + Constants.vbCrLf + " \"start\": {\"from\": 1604542428000, \"to\": 1605147228000}," + Constants.vbCrLf + " \"status\": [\"CLOSE\"]" + Constants.vbCrLf + "}" + Constants.vbCrLf);
string b64sig = GetSignatureBaseString(timeStamp, nonce);
string signature = GetSha1Hash("secret-key", b64sig);
Then I insert all the values I have into the OAuth string and make the request. It returns a 0005 auth error. About the body params, I need to have these in order to get a response. This works fine if I just use the Postman nonce, timestamp and signature so I believe it's not related to the problem though. I don't need to include the body params to generate a signature right?