I have a number of scripts that I run against my Twitter feed, stuff like bulk deleting my DMs, that sort of thing. Well of course they all stopped working last year when Twitter moved from a basic authentication model to an oAuth one. I thought it was about time I got them up and running again. Now I know there are libraries out there that will handle the Twitter oAuth stuff for me, but I wanted to get a handle on how it worked, for my own information – besides, how hard could it be, right?
Well the answer is… not that hard, but it’s tricky to get working and a total PITA to debug. So, having gotten it working I thought I’d write this blog post, mainly for my own information, so I know where to look when I forget how to do it the next time, but if it helps you guys out too then that’s cool. On the plus side, at least you don’t have to go around the loop of getting the token in the first place. If you are using code against your own account – as I’m doing here – then all the information you need is there on your app page after you register
I’ll walk you through the process, via the code, then I’ll post the full code listing below that way you can cut and paste it into your own solution if you want to use it.
Okay, so what’s first? Well, first off, we have to collect the parameters that oAuth is going to need…
//GS - Get the oAuth params
string status = "Your status goes here";
string postBody = "status=" +
Uri.EscapeDataString(status);
string oauth_consumer_key = "YourConsumerKey";
string oauth_nonce = Convert.ToBase64String(
new ASCIIEncoding().GetBytes(
DateTime.Now.Ticks.ToString()));
string oauth_signature_method = "HMAC-SHA1";
string oauth_token =
"YourToken";
TimeSpan ts = DateTime.UtcNow -
new DateTime(1970, 1, 1, 0, 0, 0, 0,DateTimeKind.Utc
);
string oauth_timestamp =
Convert.ToInt64(ts.TotalSeconds).ToString();
string oauth_version = "1.0";
Couple of tricky things here. Firstly notice two string variables, status and postBody. The latter is what we’ll use when we POST the request later, but the first is just the status part, it’s separated like this because it has to be double encoded in the signature (more on that later) but single encoded everywhere else! Yeah, I know! And that’s not really documented anywhere. I only noticed on close examination of the example text on this page.
On the subject on encoding, the .Net Framework has 4 methods of url encoding, two on the Uri class and two on the HttpUtility class. Guess what? They all do it slightly differently. The one that I’ve found that works best is Url.EscapeDataString, however, even that’s not fully RFC3986 compliant, so if you are using my code and still having problems that might be a place to look.
Moving on, the oauth_nonce param is a string that has to be unique on every call, this is to stop play back attacks on your account. Here I’ve used the ticks on the current date and time. I think this should be okay, but if not I’m sure Barry Dorrans will have a better suggestion for you. 
Next is the oauth_timestamp which is the number of seconds after the Unix epoch. Not surprisingly, there’s not a method in the .Net framework for that so you have to write your own. Remember to use the DateTime.UtcNow property to ensure it doesn’t use the local time on your machine, but uses GMT instead, regardless of where you are. This is important because, although the oAuth spec says the time has to be >= previous requests, some people are saying that, for Twitter, it has to be within 5 minutes, though I’ve not seen that in my limited use.
//GS - When building the signature string the params
//must be in alphabetical order. I can't be bothered
//with that, get SortedDictionary to do it's thing
SortedDictionary<string, string> sd =
new SortedDictionary<string, string>();
sd.Add("status", status);
sd.Add("oauth_version", oauth_version);
sd.Add("oauth_consumer_key", oauth_consumer_key);
sd.Add("oauth_nonce", oauth_nonce);
sd.Add("oauth_signature_method", oauth_signature_method);
sd.Add("oauth_timestamp", oauth_timestamp);
sd.Add("oauth_token", oauth_token);
//GS - Build the signature string
string baseString = String.Empty;
baseString += "POST" + "&";
baseString += Uri.EscapeDataString(
"http://api.twitter.com/1/statuses/update.json")
+ "&";
foreach (KeyValuePair<string,string> entry in sd)
{
baseString += Uri.EscapeDataString(entry.Key +
"=" + entry.Value + "&");
}
//GS - Remove the trailing ambersand char, remember
//it's been urlEncoded so you have to remove the
//last 3 chars - %26
baseString =
baseString.Substring(0, baseString.Length - 3);
Having gathered our parameters together, we need to form them into a string in order to sign them. Of course this isn’t straight forward either. The string has to be formed in the following pattern:
method&URL&ParamKeyValuePairs
Which looks simple enough, but it’s a little tricky. Firstly, the ampersands that split the three parts must *not* be url encoded, but the ampersands that split the parameter key value pairs *must* be url encoded. As if that wasn’t enough of a pain, the key value pairs themselves must be in alphabetical order by key, and then by value, if the key is repeated, this is because Twitter are going to replicate your signing to ensure your precious tweet hasn’t been tampered with, and so there has to be a scheme to follow for them to replicate what you did. Of course, we can use a sorted dictionary to handle that part for us. Also remember to pass status here here and not postBody as status must be double url encoded at this point, but not in the actual POST.
//GS - Build the signing key
string consumerSecret =
"yourSecret";
string oauth_token_secret =
"YourToken";
string signingKey =
Uri.EscapeDataString(consumerSecret) + "&" +
Uri.EscapeDataString(oauth_token_secret);
Next we have to create the key that we are going to instantiate the hash with. To do this you use the url encoded consumer secret, followed by a non url encoded ampersand, followed by the url encoded token. If you don’t need the token for the request you are making then leave it off, but you still need the trailing ampersand.
//GS - Sign the request
HMACSHA1 hasher = new HMACSHA1(
new ASCIIEncoding().GetBytes(signingKey));
string signatureString = Convert.ToBase64String(
hasher.ComputeHash(
new ASCIIEncoding().GetBytes(baseString)));
Next we are going to sign the request, nothing complicated here, as you see, just remember to take a base64 string of it.
//GS - Tell Twitter we don't do the 100 continue thing
ServicePointManager.Expect100Continue = false;
Then we have this line. This needs to be included to stop Twitter throwing 417 Expectation failed errors.
Well that’s about it. There’s not much to say about the rest of it. It’s just a standard POST request from here. Twitter do recommend that you send the oauth params (remember they have to reproduce what you did to verify your request) via the Authorization header, so of course, you have to url encode it all up again, and wrap it in double quotes too.
Right, that’s all I’ve got to say. I’ve posted the full code below, feel free to use it if it’s any use to you, of course it comes with no warranty whatsoever, other than to say, it works on my machine for what I use it for, YMMV. 
class Program
{
static void Main(string[] args)
{
//GS - Get the oAuth params
string status = "your status";
string postBody = "status=" +
Uri.EscapeDataString(status);
string oauth_consumer_key = "YourKey";
string oauth_nonce = Convert.ToBase64String(
new ASCIIEncoding().GetBytes(
DateTime.Now.Ticks.ToString()));
string oauth_signature_method = "HMAC-SHA1";
string oauth_token =
"YourToken";
TimeSpan ts = DateTime.UtcNow -
new DateTime(1970, 1, 1, 0, 0, 0, 0);
string oauth_timestamp =
Convert.ToInt64(ts.TotalSeconds).ToString();
string oauth_version = "1.0";
//GS - When building the signature string the params
//must be in alphabetical order. I can't be bothered
//with that, get SortedDictionary to do it's thing
SortedDictionary<string, string> sd =
new SortedDictionary<string, string>();
sd.Add("status", status);
sd.Add("oauth_version", oauth_version);
sd.Add("oauth_consumer_key", oauth_consumer_key);
sd.Add("oauth_nonce", oauth_nonce);
sd.Add("oauth_signature_method", oauth_signature_method);
sd.Add("oauth_timestamp", oauth_timestamp);
sd.Add("oauth_token", oauth_token);
//GS - Build the signature string
string baseString = String.Empty;
baseString += "POST" + "&";
baseString += Uri.EscapeDataString(
"http://api.twitter.com/1/statuses/update.json")
+ "&";
foreach (KeyValuePair<string,string> entry in sd)
{
baseString += Uri.EscapeDataString(entry.Key +
"=" + entry.Value + "&");
}
//GS - Remove the trailing ambersand char, remember
//it's been urlEncoded so you have to remove the
//last 3 chars - %26
baseString =
baseString.Substring(0, baseString.Length - 3);
//GS - Build the signing key
string consumerSecret =
"YourSecret";
string oauth_token_secret =
"YOurToken";
string signingKey =
Uri.EscapeDataString(consumerSecret) + "&" +
Uri.EscapeDataString(oauth_token_secret);
//GS - Sign the request
HMACSHA1 hasher = new HMACSHA1(
new ASCIIEncoding().GetBytes(signingKey));
string signatureString = Convert.ToBase64String(
hasher.ComputeHash(
new ASCIIEncoding().GetBytes(baseString)));
//GS - Tell Twitter we don't do the 100 continue thing
ServicePointManager.Expect100Continue = false;
//GS - Instantiate a web request and populate the
//authorization header
HttpWebRequest hwr =
(HttpWebRequest)WebRequest.Create(
@"http://api.twitter.com/1/statuses/update.json");
string authorizationHeaderParams = String.Empty;
authorizationHeaderParams += "OAuth ";
authorizationHeaderParams += "oauth_nonce=" + "\"" +
Uri.EscapeDataString(oauth_nonce) + "\",";
authorizationHeaderParams +=
"oauth_signature_method=" + "\"" +
Uri.EscapeDataString(oauth_signature_method) +
"\",";
authorizationHeaderParams += "oauth_timestamp=" + "\"" +
Uri.EscapeDataString(oauth_timestamp) + "\",";
authorizationHeaderParams += "oauth_consumer_key="
+ "\"" + Uri.EscapeDataString(
oauth_consumer_key) + "\",";
authorizationHeaderParams += "oauth_token=" + "\"" +
Uri.EscapeDataString(oauth_token) + "\",";
authorizationHeaderParams += "oauth_signature=" + "\""
+ Uri.EscapeDataString(signatureString) + "\",";
authorizationHeaderParams += "oauth_version=" + "\"" +
Uri.EscapeDataString(oauth_version) + "\"";
hwr.Headers.Add(
"Authorization", authorizationHeaderParams);
//GS - POST off the request
hwr.Method = "POST";
hwr.ContentType = "application/x-www-form-urlencoded";
Stream stream = hwr.GetRequestStream();
byte[] bodyBytes =
new ASCIIEncoding().GetBytes(postBody);
stream.Write(bodyBytes, 0, bodyBytes.Length);
stream.Flush();
stream.Close();
//GS - Allow us a reasonable timeout in case
//Twitter's busy
hwr.Timeout = 3 * 60 * 1000;
try
{
HttpWebResponse rsp = hwr.GetResponse()
as HttpWebResponse;
//GS - Do something with the return here...
}
catch (WebException e)
{
//GS - Do some clever error handling here...
}
}
}