My Photo

Technical Architect with over 15 years experience in a wide range of technologies.

@TheCodeKing

rss feed

Twitter OAuth authentication using .Net | Monday, August 29, 2011

Introduction

In this article I want to demonstrate how to implement OAuth authentication in .Net. I've previously written about my dislike of third party SDKs for social media integration and how we should leverage technology based solutions instead. One of the sticking points in doing this tends to be that implementing OAuth based authentication is relatively difficult compared with actually making the requests themselves. There is documentation available, but there seems to be a lack of .NET example code to go with it.

In keeping with my thoughts in previous articles I would recommend using open source OAuth based libraries to solve this problem, and again avoid resorting to third party Twitter/Facebook implementations which more strongly couple code to specific APIs. This keeps the solution more reusable and builds on specific technologies to better future proof your application.

I've also previously shown how client-side plugins can be used in combination with server-side code to speed development in this area. However sometimes authentication does need to be implemented purely on the server-side.

So how difficult is this?

It turns out implementing OAuth on the server-side in .Net isn't too difficult, the battle is getting the encoding and authentication signature right. With so few examples it can be a little daunting, so here's an example written in pure .NET using the official Twitter OAuth documentation and a bit of trial and error.

Background

The following example shows how to authenticate against the Twitter APIs using a registered Twitter application. Any interaction with the APIs when authenticated in this manner will behave as if coming from the Twitter account under which the application has been registered. It's therefore useful for sending out status updates or sending out notifications from a specific account.

Usually OAuth requires redirecting the user to a login screen to obtain an oAuth token which requires a bit more work. However when authenticating via a Twitter application this step is skipped as your application already has an oAuth token provided (access token). Whether you are using the application oAuth token or a user oAuth token, the following code can be used to authenticate against the twitter APIs.

The Code

The first step is to visit the Twitter developer section and register a new application. On completion you will be provided with a set of public/private keys which you will need the replace in the example below in order to run. The values I have used directly correspond with the documented example here. Make sure you replace them with your own.

var oauth_token           = "819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw";
var oauth_token_secret    = "J6zix3FfA9LofH0awS24M3HcBYXO5nI1iYe8EfBA";
var oauth_consumer_key    = "GDdmIQH6jhtmLUypg82g";
var oauth_consumer_secret = "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98";

We also need to define some details about the request. This includes a unique oauth_nonce parameter which must be generated per request, and a timestamp.

var oauth_version          = "1.0";
var oauth_signature_method = "HMAC-SHA1";
var oauth_nonce            = Convert.ToBase64String(
                                  new ASCIIEncoding().GetBytes(
                                       DateTime.Now.Ticks.ToString()));
var timeSpan               = DateTime.UtcNow
                                  - new DateTime(1970, 1, 1, 0, 0, 0, 0,
                                       DateTimeKind.Utc);
var oauth_timestamp        = Convert.ToInt64(timeSpan.TotalSeconds).ToString();
var resource_url           = "http://api.twitter.com/1/statuses/update.json";
var status                 = "Updating status via REST API if this works";

The next step is to generate an encrypted oAuth signature which Twitter will use to validate the request. To do this all of the request data is concatenated into a particular format as follows.

var baseFormat = "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method={2}" +
                "&oauth_timestamp={3}&oauth_token={4}&oauth_version={5}&status={6}";

var baseString = string.Format(baseFormat,
                            oauth_consumer_key,
                            oauth_nonce,
                            oauth_signature_method,
                            oauth_timestamp,
                            oauth_token,
                            oauth_version,
                            Uri.EscapeDataString(status)
                            );

baseString = string.Concat("POST&", Uri.EscapeDataString(resource_url), "&", Uri.EscapeDataString(baseString));

Using this base string we then encrypt the data using a composite of the secret keys and the HMAC-SHA1 algorithm.

var compositeKey = string.Concat(Uri.EscapeDataString(oauth_consumer_secret),
                        "&",  Uri.EscapeDataString(oauth_token_secret));

string oauth_signature;
using (HMACSHA1 hasher = new HMACSHA1(ASCIIEncoding.ASCII.GetBytes(compositeKey)))
{
    oauth_signature = Convert.ToBase64String(
        hasher.ComputeHash(ASCIIEncoding.ASCII.GetBytes(baseString)));
}

The oAuth signature is then used to generate the Authentication header. This requires concatenating the public keys and the token generated above into the following format.

var headerFormat = "OAuth oauth_nonce=\"{0}\", oauth_signature_method=\"{1}\", " +
                   "oauth_timestamp=\"{2}\", oauth_consumer_key=\"{3}\", " +
                   "oauth_token=\"{4}\", oauth_signature=\"{5}\", " +
                   "oauth_version=\"{6}\"";

var authHeader = string.Format(headerFormat,
                        Uri.EscapeDataString(oauth_nonce),
                        Uri.EscapeDataString(oauth_signature_method),
                        Uri.EscapeDataString(oauth_timestamp),
                        Uri.EscapeDataString(oauth_consumer_key),
                        Uri.EscapeDataString(oauth_token),
                        Uri.EscapeDataString(oauth_signature),
                        Uri.EscapeDataString(oauth_version)
                );

We are now ready to send the request, which is the easy part. Note we must also disable the Expect: 100-Continue header using the ServicePointManager. Without this code the .Net sends the header by default which is not supported by Twitter.

var postBody = "status=" + Uri.EscapeDataString(status);

ServicePointManager.Expect100Continue = false;

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(resource_url);
request.Headers.Add("Authorization", authHeader);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
using (Stream stream = request.GetRequestStream())
{
    byte[] content = ASCIIEncoding.ASCII.GetBytes(postBody);
    stream.Write(content, 0, content.Length);
}
WebResponse response = request.GetResponse();

Summary

This example hopefully shows how OAuth can be implemented with fairly little effort. In the example provided I've kept everything inline for clarity, however in the real world you would obviously refactor the code into more sensible layers.

In this way it's possible to build some highly testable lightweight classes in order to generate the required message signature, make the requests, and handle the response.


2 comments:

Russ said...

I've hit problems in the past where the URL encoding of the framework doesn't agree with the encoding that twitter is using, resulting in signature mismatches. Multibyte characters are another can of worms. Ended up using a v. simple library from Cropper Plugins. More here: http://cropperplugins.codeplex.com/discussions/249415

TheCodeKing said...

Interesting, this is the hardest part of oAuth. To get my version working I tested against the documented version on Twitter which used multibyte characters. It gave the same oAuth signature so I'm guessing it's OK. I haven't done any thorough testing though, it was more an exercise in what's involved. Using an open source OAuth library is a wise way to go.

Post a Comment