2016-11-18

WooCommerce API Using RestSharp Over HTTP With OAuth

Now after searching NuGet I saw that there is a C# WooCommerce library out there all ready written called WooCommerce.NET. This isn’t going to work for me because I will need the ability to gain access to custom Order fields.

So I decided to just use my favorite REST client RestSharp to contact a fairly simple REST API for WooCommerce. But I was running into a strange authentication issue and being denied authentication with a 401 status code.

Now, first a disclaimer… DO NOT USE HTTP FOR WooCommerce IN A PRODUCTION ENVIRONMENT!

Ok, but I need to do some testing in a local environmnet so I can see that my code is working properly. Reading the WooCommerce documentation for authentication, which can be found here, it clearly states:

The OAuth parameters must be added as query string parameters and not included in the Authorization header. This is because there is no reliable cross-platform way to get the raw request headers in WordPress.

So I do a bit of digging about RestSharp and OAuth1.0 and come up with this suite of tests. Here’s the important bit that I needed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[Test]
public void Can_Authenticate_OAuth1_With_Querystring_Parameters()
{
const string consumerKey = "enterConsumerKeyHere";
const string consumerSecret = "enterConsumerSecretHere";
const string baseUrl = "http://restsharp.org";
var expected = new List<string>
{
"oauth_consumer_key",
"oauth_nonce",
"oauth_signature_method",
"oauth_timestamp",
"oauth_version",
"oauth_signature"
};
RestClient client = new RestClient(baseUrl);
RestRequest request = new RestRequest(Method.GET);
var authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret);
authenticator.ParameterHandling = OAuthParameterHandling.UrlOrPostParameters;
authenticator.Authenticate(client, request);
var requestUri = client.BuildUri(request);
var actual = HttpUtility.ParseQueryString(requestUri.Query).AllKeys.ToList();
Assert.IsTrue(actual.SequenceEqual(expected));
}

The above Authenticate method will do all the work and add all the parameters I need.

But wait… Something’s not right here. In the debugger it shows me that the parameters on the request are being added as cookes:

Visual Studio Debugger

Strange, but ok. So I decided to make an extension method that does all the authentication, gets all the parameters added to the request, and then converts them to QueryString parameters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static class RestRequestExtensions
{
public static RestRequest BuildOAuth1QueryString(this RestRequest request, RestClient client, string consumerKey, string consumerSecret)
{
var auth = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret);
auth.ParameterHandling = OAuthParameterHandling.UrlOrPostParameters;
auth.Authenticate(client, request);
//convert all these oauth params from cookie to querystring
request.Parameters.ForEach(x =>
{
if (x.Name.StartsWith("oauth_"))
x.Type = ParameterType.QueryString;
});
return request;
}
}

So the code to build a request now looks something like this.

1
2
3
4
var client = new RestClient("http://example.com/api/v1");
var request = new RestRequest("/path/to/resource");
request.BuildOAuth1QueryString(client, "{consumer_key}", "{consumer_secret}");
var response = client.Execute(request);

And there we have it. Talking to WooCommerce with RestSharp.

For Production

I can not stress enough to not communicate over HTTP in production. In production, you should be using HTTPS. In that case you can use HTTP Basic Authentication. Then you will no longer need the BuildOAuth1QueryString extension method, you would simply add the Basic Authentication to the client like so:

1
2
//Basic over Https
client.Authenticator = new HttpBasicAuthenticator("{consumer_key}", "{consumer_secret}");

Hope this helps!

View Comments
2009-04-27

The HttpWebRequest and Using Client Certificates

So you may have found yourself in a similar situation, needing to make a TCP/IP request to a 3rd party API possibly using SSL. Well, that is a quite simple task. It can however, be complicated if this 3rd party requires the use of certificates for communication to its API server. I found myself in some sort of certificate hell where I had the certificate, added it to the request and somehow it still wasn’t working. If you know what I’m talking about and had as many hurdles as I did, my condolences to you. I will try to explain in this article how I started, the problems I ran into and then the overall solution that ended up working for me.

To start with, you should have some kind of certificate. Most likely a .pfx or .p12 file. This can also come with a private key or password for the certificate’s encryption. This is what a standard WebRequest over SSL might look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public string GetData(string inputData)
{
//will hold the result
string result = string.Empty;
//build the request object
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://someapi.com/");
//write the input data (aka post) to a byte array
byte[] requestBytes = new ASCIIEncoding().GetBytes(inputData);
//get the request stream to write the post to
Stream requestStream = request.GetRequestStream();
//write the post to the request stream
requestStream.Write(requestBytes, 0, requestBytes.Length);
//build a response object to hold the response
//submit the request by calling get response
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
//get the response stream to read the response from
Stream responseStream = response.GetResponseStream();
//now read it out to our result
using (StreamReader rdr = new StreamReader(responseStream))
{
//set the result to the contents of the stream
result = rdr.ReadToEnd();
}
//return
return result;
}

The example above is missing the portion where you add the certificate to the request. You may receive a 403 Forbidden error from the server if a certificate is required to make the request to the API server. A simple way of adding a certificate to the request would be like so:

1
2
//add certificate to the request
request.ClientCertificates.Add(new X509Certificate(@"C:\certs\Some Cert.p12", @"SecretP@$$w0rd"));

The X509Certificate class is found in the System.Security.Cryptography.X509Certificates namespace. Simply add a new certificate to the client certificates before calling for the response, and it should be sent with the request. However, you may encounter an exception with the message “The system cannot find the file specified”. I encountered this error after I got the application off my local machine and onto the development server. After doing some research I stumbled upon this kb article. This article opened my eyes to how using certificates is a little more complicated than I initially thought. Turns out the problem was that the user trying to access the certificate does not have a profile loaded.

After stepping through the article, installing the certificate to the local machine’s personal certificate store, and then granting rights to the certificate using the WinHttpCertCfg.exe tool, and putting in a little more code found in the kb article, I was well on my way. The article describes how to use C# to open a certificate store and use the certificate directly out of the store. This presents a bit more elegant, and in my opinion more secure, way of getting to and using the certificate.

1
2
3
4
5
6
//add it in a better way
X509Store certStore = new X509Store("My", StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2 cert = certStore.Certificates.Find(X509FindType.FindBySubjectName, "My cert subject", false)[0];
certStore.Close();
request.ClientCertificates.Add(cert);

This method will not only give access to the certificate regardless of having a loaded profile, but it also takes the certificate’s private key password out of the code and/or configuration. This snippet above took me out of the certificate hell that was crushing my life for a couple days!

Putting it all together:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public string GetData(string inputData)
{
//will hold the result
string result = string.Empty;
//build the request object
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://someapi.com/");
//add certificate to the request
//request.ClientCertificates.Add(new X509Certificate(@"C:\certs\Some Cert.p12", @"SecretP@$$w0rd"));
//add it in a better way
X509Store certStore = new X509Store("My", StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2 cert = certStore.Certificates.Find(X509FindType.FindBySubjectName, "My cert subject", false)[0];
certStore.Close();
request.ClientCertificates.Add(cert);
//write the input data (aka post) to a byte array
byte[] requestBytes = new ASCIIEncoding().GetBytes(inputData);
//get the request stream to write the post to
Stream requestStream = request.GetRequestStream();
//write the post to the request stream
requestStream.Write(requestBytes, 0, requestBytes.Length);
//build a response object to hold the response
//submit the request by calling get response
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
//get the response stream to read the response from
Stream responseStream = response.GetResponseStream();
//now read it out to our result
using (StreamReader rdr = new StreamReader(responseStream))
{
//set the result to the contents of the stream
result = rdr.ReadToEnd();
}
//return
return result;
}

Hope this helps!

View Comments