In this post I'll be showing how to post a Tweet to Twitter using C# and no 3rd party libraries. This is a pure .NET approach, and it's more time consuming but it also gives you a much deeper understanding of how oAuth and Twitter work together behind the scenes. I'll be breaking down the entire process step by step from generating the appropriate tokens to the final signed request getting sent off. As of me writing this, Twitter's REST API is on version 1.1, so it isn't future proof, but for now it'll do the job.
Overview
The REST API's give developers programmatic access to read and write Tweets. This of course requires some type of authentication from the user. The REST API identifies Twitter applications and users using OAuth and responses are available in JSON format.
Posting a tweet programmatically comes down to selecting the corresponding endpoint, and creating a very specific and signed authentication header along with your request. You will need to create a Twitter application before you do anything however, as you do need the corresponding Consumer Token and Access Tokens. And for that, you'll need to create a Twitter Application.
Create A New Application
You can create applications for Twitter here.
You'll need to give your application a name, a description, a website and a callback url. Because I'll be using the API to post tweets for my own personal application, this data isn't publicly visible, but it is still required.
Important: You need to add something to the Callback URL textbox. Even if it doesn't apply to you. The good news is you can add anything really. I added the same URL that was entered into the Website field for example. Once you're done creating an application, you'll be taken to your applications setting page which will some very important info, that you probably shouldn't share with many people.
Important 2: Make sure that your application is set to both read and write under the Access Level, otherwise you will receive an Un-Authorized error message when attempting to post anything to Twitter.
Not done quite just yet. In order to post Tweets on your own behalf, that is, on the account that owns the application, you'll need to use the Single User OAuth capabilities of the Twitter API. So you'll need to generate your own personal Access Tokens. And those tokens can be created in the settings page for your application as well.
These are very important keys and tokens and as such, should not be shared with anyone. These will be the tokens that you will use to sign your requests later on and to make posts on your own behalf. Now that we have these keys, we can move on to implementing the code.
A Few Notes
In order to cut back on abusing the API, Twitter does set some limiting rates on the number of posts that you are allowed per certain time frame. Rates are limited per access token in your possession. For POSTs, there is no maximum limit of of requests set, however there is a limit to the number of posts that you can send at any one time. Hitting that limit will cause a HTTP 403 error to occur.
The Twitter API will also return an error if it detects that your tweet is a duplicate of a recently published tweet.
Authorizing A Twitter Request
Now that the boilerplate is out of the way, we can get started. In order to POST tweets, you'll need to make a request to the following URL.:
https://api.twitter.com/1.1/statuses/update.json
And the HTTP request will look something like this.
POST /1/statuses/update.json?include_entities=true HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.4
Content-Type: application/x-www-form-urlencoded
Content-Length: 76
Host: api.twitter.com
Any programming framework can generate this request with little problem, and in .NET it's no different. This request isn't complete however, as it's missing many many security features that will allow Twitter to tell that a valid person is making that request. And for that, Twitter relies on the oAuth 1.0 protocol. Which means we'll need to add a new request parameter Authorization to our collection. This is where the remainder of the post will focus on, as it is somewhat complex and time consuming.
At the end, the request should look something like the following:
POST /1/statuses/update.json?include_entities=true HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.4
Content-Type: application/x-www-form-urlencoded
Authorization:
OAuth oauth_consumer_key="lakjg3jalkjFJkjLkjl4k",
oauth_nonce="lfjlktlk265234lkj3l4j534jk5lkj345",
oauth_signature="t4kn3lkkjlk3jlklk3lk3jhlkh%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318622958",
oauth_token="588585756-j4lktlk32l4lkj4l2j5l345LKJHDL",
oauth_version="1.0"
Content-Length: 76
Host: api.twitter.com
Note the new Authorization header being added. It is comprised of 7 key/value pairs of data that are prefixed with "oauth".
Here is a quick summary of each property, and how to generate them using C#. Most are pretty straight forward, except for the oauth_signature, which I'll cover at the end as it's the most complex and most important.
consumer_key: The consumer key identifies the application making the request, and if you followed the steps up above creating your application, you can get that value from your applications setting page.
oauth_nonce: The nonce parameter, or "nonsense" parameter, is a random alphanumeric string that must be unique per request. It essentially tells Twitter if a request has been submitted more than once. Any random alphanumeric character will work here really, so it's up to you how you will take that approach. You can declare a GUID for this, or simply generate a random length and random character string like I did.
private string GenerateNonce()
{
string nonce = string.Empty;
var rand = new Random();
int next = 0;
for (var i = 0; i < 32; i++)
{
next = rand.Next(65, 90);
char c = Convert.ToChar(next);
nonce += c;
}
return nonce;
}
oauth_signature:
See down below:
oauth_signature_method: The signature method used by the Twitter API is "HMAC-SHA1".
oauth_timestamp:
The timestamp value indicates when the request was created. This value should be the number of seconds since the Unix epoch at the point the request is generated. Twitter will reject requests which were created too far in the past, so it is important to keep the clock of the computer generating requests in sync with NTP.
The following function in C# will generate the appropriate timestamp.
public double GenerateTimestamp(DateTime date)
{
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
TimeSpan diff = date.ToUniversalTime() - origin;
return Math.Floor(diff.TotalSeconds);
}
oauth_token:
There are various ways to get tokens in order to make requests. In the scenario where a single user wants to post to their own timeline, such as what I'll be doing, you can generate native tokens right from your applications settings screen. These tokens can be deleted or reset later on if need be, and once again, should be kept secret.
oauth_version:
The oauth_version parameter should always be 1.0 for any request sent to the Twitter API.
The following function will generate the appropriate authorization header. It is essentially a string beginning with "OAuth", followed by the 7 specified oauth parameters percent encoded.
private string GenerateAuthorizationHeader(string status)
{
string signatureMethod = "HMAC-SHA1";
string version = "1.0";
string nonce = GenerateNonce();
double timestamp = ConvertToUnixTimestamp(DateTime.Now);
string dst = string.Empty;
dst = string.Empty;
dst += "OAuth ";
dst += string.Format("oauth_consumer_key=\"{0}\", ", Uri.EscapeDataString(oAuthConsumerKey));
dst += string.Format("oauth_nonce=\"{0}\", ", Uri.EscapeDataString(nonce));
dst += string.Format("oauth_signature=\"{0}\", ", Uri.EscapeDataString(GenerateOauthSignature(status, nonce, timestamp.ToString())));
dst += string.Format("oauth_signature_method=\"{0}\", ", Uri.EscapeDataString(signatureMethod));
dst += string.Format("oauth_timestamp=\"{0}\", ", timestamp);
dst += string.Format("oauth_token=\"{0}\", ", Uri.EscapeDataString(accessToken));
dst += string.Format("oauth_version=\"{0}\"", Uri.EscapeDataString(version));
return dst;
}
Generating The Signature
I left the oauth_signature section on top blank on purpose, because it's more complicated and requires its own area. The signature parameter is percent encoded collection of the authentication parameters and the Twitter message you wish to send, that will be HMAC SHA1 encoded and signed using a specific key.This signature will be sent along with the authorized request above in order to prove that the request wasn't tampered with and that it belongs to the appropriate application.
The signature is comprised of a few different elements, which I'll break down below. Combined and then encrypted, this string will become the signature used in the authentication request.
Request Method and URL
For the purposes of sending a tweet, our request method will be a POST, and the base URL will be:
https://api.twitter.com/1/statuses/update.json
Parameters
There are 7 parameters that we'll need to percent encode and concatenate together using the '&'. These will be the same key/values that we generated for our main authentication request above. That is, the same nonce, timestamp and same tokens. You'll also need your actualy message that you wish to tweet and percent encode that as well under the status parameter. The oAuth specification requires that these values be sorted lexigraphically, that is in alphabetical order.
string dst = string.Empty;
dst += string.Format("oauth_consumer_key={0}&", Uri.EscapeDataString(oAuthConsumerKey));
dst += string.Format("oauth_nonce={0}&", Uri.EscapeDataString(nonce));
dst += string.Format("oauth_signature_method={0}&", Uri.EscapeDataString(signatureMethod));
dst += string.Format("oauth_timestamp={0}&", timestamp);
dst += string.Format("oauth_token={0}&", Uri.EscapeDataString(oauthToken));
dst += string.Format("oauth_version={0}&", Uri.EscapeDataString(version));
dst += string.Format("status={0}", Uri.EscapeDataString(status));
Signature Base String
The signature base string is our final string value put together, before it is signed and encrypted. It is comprised of the request method, base url, and the parameter string which we just created above. The steps are as follows:
- Convert the HTTP Method to uppercase and set the output string equal to this value.
- Append the ‘&’ character to the output string.
- Percent encode the URL and append it to the output string.
- Append the ‘&’ character to the output string.
- Percent encode the parameter string and append it to the output string.
That will result in something like this:
POST&
https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json
&oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521
Signing Key
Next up, we need to create a key in order to sign our HMAC-SHA1 hash. This key is comprised the oAuth Consumer Secret percent encoded, followed by an '&', followed by the oAuth access token secret also percent encoded. This string combined will become our key.
string signingKey = string.Empty;
signingKey = string.Format("{0}&{1}", Uri.EscapeDataString(oAuthConsumerSecret),Uri.EscapeDataString(accessTokenSecret));
HMAC-SHA1
The last step in generating our signature is to hash our signature base string. And for that we'll need to use the HMACSHA1 class in .NET.
HMACSHA1 hmac = new HMACSHA1();
hmac.Key = Encoding.UTF8.GetBytes(signingKey);
byte[] databuff = System.Text.Encoding.UTF8.GetBytes(signature_base_string);
byte[] hashbytes = hmac.ComputeHash(databuff);
return Convert.ToBase64String(hashbytes);
Remember to base 64 encode the final result of the computed hash.
Sending The Final Request
This is the last piece in our puzzle, and we now have a complete authorization header to send off to Twitter.
private void SendTweet(string message)
{
string authHeader = GenerateAuthorizationHeader(message);
string postBody = "status=" + Uri.EscapeDataString(message);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
HttpWebRequest authRequest = (HttpWebRequest)WebRequest.Create(oAuthUrl);
authRequest.Headers.Add("Authorization", authHeader);
authRequest.Method = "POST";
authRequest.UserAgent = "OAuth gem v0.4.4";
authRequest.Host = "api.twitter.com";
authRequest.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
authRequest.ServicePoint.Expect100Continue = false;
authRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
using (Stream stream = authRequest.GetRequestStream())
{
byte[] content = Encoding.UTF8.GetBytes(postBody);
stream.Write(content, 0, content.Length);
}
WebResponse authResponse = authRequest.GetResponse();
authResponse.Close();
}
Update 2019: Starting on July 25th, 2019 Twitter began requiring TLS 1.2 as the security protocol. https://twittercommunity.com/t/removing-support-for-legacy-tls-versions-1-0-1-1-on-twitter/126648
It's a little time consuming and there are for sure easier ways to post a tweet using code. There are several 3rd party libraries that get the job done much faster. But this is the most barebones way to do it. It doesn't rely on any external code, and it doesn't rely on the built-in oAuth classes that .NET offers even. The full source is down below ready for your copy and paste pleasure. Keys and tokens should be specified in your web.config's app settings.
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace TestOauth
{
public partial class PostTweet : System.Web.UI.Page
{
private string oAuthConsumerKey = ConfigurationManager.AppSettings["oAuthConsumerKey"];
private string oAuthConsumerSecret = ConfigurationManager.AppSettings["oAuthConsumerSecret"];
private string accessToken = ConfigurationManager.AppSettings["AccessToken"];
private string accessTokenSecret = ConfigurationManager.AppSettings["AccessTokenSecret"];
string oAuthUrl = "https://api.twitter.com/1.1/statuses/update.json";
protected void Page_Load(object sender, EventArgs e)
{
}
private void SendTweet(string message)
{
string authHeader = GenerateAuthorizationHeader(message);
string postBody = "status=" + Uri.EscapeDataString(message);
HttpWebRequest authRequest = (HttpWebRequest)WebRequest.Create(oAuthUrl);
authRequest.Headers.Add("Authorization", authHeader);
authRequest.Method = "POST";
authRequest.UserAgent = "OAuth gem v0.4.4";
authRequest.Host = "api.twitter.com";
authRequest.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
authRequest.ServicePoint.Expect100Continue = false;
authRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
using (Stream stream = authRequest.GetRequestStream())
{
byte[] content = Encoding.UTF8.GetBytes(postBody);
stream.Write(content, 0, content.Length);
}
WebResponse authResponse = authRequest.GetResponse();
authResponse.Close();
}
private string GenerateAuthorizationHeader(string status)
{
string signatureMethod = "HMAC-SHA1";
string version = "1.0";
string nonce = GenerateNonce();
double timestamp = ConvertToUnixTimestamp(DateTime.Now);
string dst = string.Empty;
dst = string.Empty;
dst += "OAuth ";
dst += string.Format("oauth_consumer_key=\"{0}\", ", Uri.EscapeDataString(oAuthConsumerKey));
dst += string.Format("oauth_nonce=\"{0}\", ", Uri.EscapeDataString(nonce));
dst += string.Format("oauth_signature=\"{0}\", ", Uri.EscapeDataString(GenerateOauthSignature(status, nonce, timestamp.ToString())));
dst += string.Format("oauth_signature_method=\"{0}\", ", Uri.EscapeDataString(signatureMethod));
dst += string.Format("oauth_timestamp=\"{0}\", ", timestamp);
dst += string.Format("oauth_token=\"{0}\", ", Uri.EscapeDataString(accessToken));
dst += string.Format("oauth_version=\"{0}\"", Uri.EscapeDataString(version));
return dst;
}
private string GenerateOauthSignature(string status, string nonce, string timestamp)
{
string signatureMethod = "HMAC-SHA1";
string version = "1.0";
string result = string.Empty;
string dst = string.Empty;
dst += string.Format("oauth_consumer_key={0}&", Uri.EscapeDataString(oAuthConsumerKey));
dst += string.Format("oauth_nonce={0}&", Uri.EscapeDataString(nonce));
dst += string.Format("oauth_signature_method={0}&", Uri.EscapeDataString(signatureMethod));
dst += string.Format("oauth_timestamp={0}&", timestamp);
dst += string.Format("oauth_token={0}&", Uri.EscapeDataString(accessToken));
dst += string.Format("oauth_version={0}&", Uri.EscapeDataString(version));
dst += string.Format("status={0}", Uri.EscapeDataString(status));
string signingKey = string.Empty;
signingKey = string.Format("{0}&{1}", Uri.EscapeDataString(oAuthConsumerSecret), Uri.EscapeDataString(accessTokenSecret));
result += "POST&";
result += Uri.EscapeDataString(oAuthUrl);
result += "&";
result += Uri.EscapeDataString(dst);
HMACSHA1 hmac = new HMACSHA1();
hmac.Key = Encoding.UTF8.GetBytes(signingKey);
byte[] databuff = System.Text.Encoding.UTF8.GetBytes(result);
byte[] hashbytes = hmac.ComputeHash(databuff);
return Convert.ToBase64String(hashbytes);
}
private string GenerateNonce()
{
string nonce = string.Empty;
var rand = new Random();
int next = 0;
for (var i = 0; i < 32; i++)
{
next = rand.Next(65, 90);
char c = Convert.ToChar(next);
nonce += c;
}
return nonce;
}
public static double ConvertToUnixTimestamp(DateTime date)
{
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
TimeSpan diff = date.ToUniversalTime() - origin;
return Math.Floor(diff.TotalSeconds);
}
}
}