I'm querying an API that uses OAuth1. I can make a query successfully using RestSharp:
var client = new RestClient("https://api.thirdparty.com/1/");
client.Authenticator =
OAuth1Authenticator.ForAccessToken(appKey, appSecret, token, tokenSecret);
var request = new RestRequest("projects/12345/documents", Method.GET);
request.AddParameter("recursive", "true");
var response = client.Execute(request);
Unfortunately I'm unable to use RestSharp in the actual project (I'm just using it here to verify that the API call works) so I've tried to get OAuth working using just plain .NET.
I have it working for requests that do not utilise query parameters, so a straightforward GET
to https://api.thirdparty.com/1/projects/12345/documents
works just fine.
As soon as I try to use a query parameter such as https://api.../documents?recursive=true
as shown in the RestSharp sample I get a 401 Unauthorized
error as I think my OAuth signature is not valid.
Here is how I'm generating the OAuth signature and request. Can anybody tell me how to generate a valid signature when query parameters are involved?
static string appKey = @"e8899de00";
static string appSecret = @"bffe04d6";
static string token = @"6e85a21a";
static string tokenSecret = @"e137269f";
static string baseUrl = "https://api.thirdparty.com/1/";
static HttpClient httpclient;
static HMACSHA1 hasher;
static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
static void Main(string[] args)
{
// SETUP HTTPCLIENT
httpclient = new HttpClient();
httpclient.BaseAddress = new Uri(baseUrl);
httpclient.DefaultRequestHeaders.Accept.Clear();
httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// SETUP THE HASH MESSAGE AUTHENTICATION CODE (HMAC) WITH SHA1
hasher = new HMACSHA1(new ASCIIEncoding().GetBytes(string.Format("{0}&{1}", appSecret, tokenSecret)));
// WORKS IF QUERY PARAMETER IS MISSED OFF THE END, FAILS WITH 401 IF INCLUDED
var document =
Request(HttpMethod.Get, "projects/12345/documents?recursive=true");
}
static string Request(HttpMethod method, string url)
{
// CREATE A TIMESTAMP OF CURRENT EPOCH TIME
var timestamp = (int)((DateTime.UtcNow - epoch).TotalSeconds);
// DICTIONARY WILL HOLD THE KEY/PAIR VALUES FOR OAUTH
var oauth = new Dictionary<string, string>();
oauth.Add("oauth_consumer_key", appKey);
oauth.Add("oauth_signature_method", "HMAC-SHA1");
oauth.Add("oauth_timestamp", timestamp.ToString());
oauth.Add("oauth_nonce", "nonce");
oauth.Add("oauth_token", token);
oauth.Add("oauth_version", "1.0");
// GENERATE OAUTH SIGNATURE
oauth.Add("oauth_signature", GenerateSignature(method.ToString(), string.Concat(baseUrl, url), oauth));
// GENERATE THE REQUEST
using (var request = new HttpRequestMessage())
{
// URL AND METHOD
request.RequestUri = new Uri(string.Concat(baseUrl, url));
request.Method = method;
// GENERATE AUTHORIZATION FOR THIS REQUEST
request.Headers.Add("Authorization", GenerateOAuthHeader(oauth));
// MAKE REQUEST
var response = httpclient.SendAsync(request).Result;
// ENSURE IT WORKED
response.EnsureSuccessStatusCode(); // THROWS 401 UNAUTHORIZED
// RETURN CONTENT
return response.Content.ReadAsStringAsync().Result;
};
}
static string GenerateSignature(string verb, string url, Dictionary<string, string> data)
{
var signaturestring = string.Join(
"&",
data
.OrderBy(s => s.Key)
.Select(kvp => string.Format(
"{0}={1}",
Uri.EscapeDataString(kvp.Key),
Uri.EscapeDataString(kvp.Value))
)
);
var signaturedata = string.Format(
"{0}&{1}&{2}",
verb,
Uri.EscapeDataString(url),
Uri.EscapeDataString(signaturestring.ToString())
);
return Convert.ToBase64String(hasher.ComputeHash(new ASCIIEncoding().GetBytes(signaturedata.ToString())));
}
static string GenerateOAuthHeader(Dictionary<string, string> data)
{
return "OAuth " + string.Join(
", ",
data
.Select(kvp => string.Format("{0}=\"{1}\"", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)))
.OrderBy(s => s)
);
}