API Call Using Twitter’s Application-only Authentication

I’ve a side project that has been put on hold for a while and I decided to pick it up last week. It’s a small, two-part web app. One part pulls data from Twitter.

In order to make authorized calls to Twitter API, an application must first obtain access tokens from Twitter. There are two ways to do this: OAuth access tokens in behalf of the user or Application-only Authentication. I pondered on writing my own full-pledge OAuth client library but it’s an overkill for my requirements. I settled on Application-only Authentication.

Writing a client library for Twitter sounds fun and cool until you actually do it. It’s meticulous and finicky. In fact, scouring the Internet for a working code was a fruitless endeavor.  Half of my Internet searches yielded half-baked answers. The other half suggested that I should just use third-party libraries which I was adamant about—I don’t want to miss the opportunity to learn in this project. Desperation led me to Twitter’s documentation: I have to do everything from scratch without any help from StackOverflow.

I use HttpClient for everything that’s HTTP—API call, file upload, etc.—it provides better granularity when sending and receiving HTTP requests and response, respectively. Despite using HttpClient for a while now, using it for OAuth tested my patience. Letting an API specification dictate my code is not fun at all and mostly trial-and-error. To make things worst, the errors are often cryptic or vague.

Several hours later, I was able to pull together a working build. Application-only Authorization is comprise of three parts: preparing your keys, retrieving the bearer token and sending actual API call.

Preparing Your Keys
Application-only Authentication has a limitation: it does not have user context so it has a few limitations. However, contrary to what most developers believe, it’s often suffice to most situation. Here’s how to prepare consumer key and consumer secret key when sending an HTTP request (excerpt from Twitter):

  1. URL encode the consumer key and the consumer secret according to RFC 1738. Note that at the time of writing, this will not actually change the consumer key and secret, but this step should still be performed in case the format of those values changes in the future.
  2. Concatenate the encoded consumer key, a colon character “:”, and the encoded consumer secret into a single string.
  3. Base64 encode the string from the previous step.
    var encodedConsumerKey       = HttpUtility.UrlEncode(_ConsumerKey);
    var encodedConsumerKeySecret = HttpUtility.UrlEncode(_ConsumerKeySecret);
    var encodedPair              = Base64Encode(String.Format("{0}:{1}", encodedConsumerKey, encodedConsumerKeySecret));

Retrieving Bearer Token
This is where I did a lot of trial-and-error. Preparing and sending the request requires a good understanding of how HTTP requests work. Converting that knowledge to .NET/C# is challenging if you’re inexperienced.

  • The request must be a HTTP POST request.
  • The request must include an Authorization header with the value of Basic <base64 encoded value from step 1>.
  • The request must include a Content-Type header with the value of application/x-www-form-urlencoded;charset=UTF-8.
  • The body of the request must be grant_type=client_credentials.
    var requestToken = new HttpRequestMessage {
        Method      = HttpMethod.Post,
        RequestUri  = new Uri("oauth2/token", UriKind.Relative),
        Content     = new StringContent("grant_type=client_credentials")
    };
 
    requestToken.Content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded") { CharSet = "UTF-8" };
    requestToken.Headers.TryAddWithoutValidation("Authorization", String.Format("Basic {0}", encodedPair));

Making the Actual API Call
This is the easy part. Once you have the bearer token, just add an `Authorization` header to the request with the bearer token as its value and do a `Post` call.

    requestData.Headers.TryAddWithoutValidation("Authorization", String.Format("Bearer {0}", bearerToken));
 
    var results = await HttpClient.SendAsync(requestData);
    return await results.Content.ReadAsStringAsync();

Here’s the full working method:

        public override async Task Post(string path, HttpContent content) {
 
            var bearerToken = await GetToken();
 
            if (bearerToken == null || bearerToken == string.Empty)
                throw new Exception("Bearer token cannot  be empty");
 
            var requestData = new HttpRequestMessage{
                                    Method      = HttpMethod.Post,
                                    Content     = content,
                                    RequestUri  = new Uri(path, UriKind.Relative),
                                };
 
            requestData.Headers.TryAddWithoutValidation("Authorization", String.Format("Bearer {0}", bearerToken));
 
            var results = await HttpClient.SendAsync(requestData);
            return await results.Content.ReadAsStringAsync();
        }
 
 
        private async Task GetToken() {
 
            if (_ConsumerKey == null || _ConsumerKey == string.Empty)
                throw new Exception("No Consumer Key found.");
 
            if (_ConsumerKeySecret == null || _ConsumerKeySecret == string.Empty)
                throw new Exception("No Consumer Secret Key found.");
 
            var encodedConsumerKey       = HttpUtility.UrlEncode(_ConsumerKey);
            var encodedConsumerKeySecret = HttpUtility.UrlEncode(_ConsumerKeySecret);
            var encodedPair              = Base64Encode(String.Format("{0}:{1}", encodedConsumerKey, encodedConsumerKeySecret));
 
            var requestToken = new HttpRequestMessage {
                Method      = HttpMethod.Post,
                RequestUri  = new Uri("oauth2/token", UriKind.Relative),
                Content     = new StringContent("grant_type=client_credentials")
            };
 
            requestToken.Content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded") { CharSet = "UTF-8" };
            requestToken.Headers.TryAddWithoutValidation("Authorization", String.Format("Basic {0}", encodedPair));
 
            var bearerResult    = await HttpClient.SendAsync(requestToken);
            return JObject.Parse(await bearerResult.Content.ReadAsStringAsync())["access_token"].ToString();
        }
 
        private static string Base64Encode(string plainText) {
            var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
            return System.Convert.ToBase64String(plainTextBytes);
        }