Posting to X (Twitter) with OAuth 1.0
Photo by Fachrizal Maulana on Unsplash

Posting to X (Twitter) with OAuth 1.0

Table of contents

Introduction

As I start to post more frequently again, I've been utilizing Zapier to push my blogs to Facebook, LinkedIn, and Medium. The X / Twitter zap is no longer available however, due to recent API changes.

Not willing to accept that, and considering the API is still free for up to 1500 tweets / month, I decided to build my own blog integration. The X API utilizes OAuth 1.0 for tweet management authentication, which I hadn't worked with before.

It seemed simple enough, given the post man collection that's provided, but understanding and figuring out how to create the authentication token was more difficult and time consuming that expected.

When using the Postman collection, the generated token looks something like this:

OAuth oauth_consumer_key=XXX, oauth_token=XXX-YYY, oauth_nonce=XXX, oauth_signature_method=HMAC-SHA1, oauth_version=1.0, oauth_timestamp=XXX, oauth_signature=XXX

I ended up having to go through the official documentation as well as 2 or 3 different blog posts to actually get it working in C#, so I thought I'd detail my steps here for others who may be encountering the same issue.

Create a Project and App

Before we can make any requests, we need to create a project and application from the twitter developer portal. Once the app has been created, we'll need to create and save 4 different tokens:

  • API Key
  • API Secret
  • Access Token
  • Access Secret

These can be found by going to the keys and tokens page within the developer portal.

Generate the Auth Token

The main piece for authenticating with OAuth 1.0 is creating a signature, and there are several parts to it.

Gather all the Data

To generate a signature, we first need to collect all the different pieces used within the signature.

  • oauth_consumer_key: This is the API Key for the app
  • oauth_signature_method: HMAC-SHA1
  • oauth_timestamp: This is a user-generated timestamp
  • oauth_nonce: This is a unique user-generated string
  • oauth_token: This is the Access Token for the app
  • oauth_version: 1.0
Dictionary<string, string> data = new()
{
  { "oauth_consumer_key", _settings.ApiKey },
  { "oauth_signature_method", "HMAC-SHA1" },
  { "oauth_timestamp", timestamp.ToString() },
  { "oauth_nonce", Guid.NewGuid().ToString() },
  { "oauth_token", _settings.AccessToken },
  { "oauth_version", "1.0" },
};

Generate a signature

The next step is to generate a signature, and to do that we need the full URL of the request in addition to the oath keys from our dictionary.

First we combine all of our URI encoded OAuth values alphabetically in a single string, joined with "&"

string? signatureString = string.Join(
  "&", 
  data
    .Union(data)
    .Select(kvp => string
      .Format(
        "{0}={1}", 
        Uri.EscapeDataString(kvp.Key), 
        Uri.EscapeDataString(kvp.Value)
      )
    )
    .OrderBy(s => s)
);
⚠️
Some posts I saw, including the official documentation, make it seem as though the url parameters AND body parameters are included with this data. I continued to get unauthenticated results until omitting the body parameters though.

Next, we combine the signature string with the full URL of the request and the HTTP method being used

string? fullSignatureData = string.Format(
  "{0}&{1}&{2}",
  "POST",
  Uri.EscapeDataString(url),
  Uri.EscapeDataString(signatureString ?? string.Empty)
);

The last part is to hash everything using HMAC-SHA1 and base64 encode the result. The hasher is created using the API Secret and Access Token Secret

string secretKey = string.Format(
  "{0}&{1}", 
  _settings.ApiSecret, 
  _settings.AccessTokenSecret
);

byte[] secretKeyBytes = new ASCIIEncoding().GetBytes(secretKey);
_hasher = new HMACSHA1(secretKeyBytes);

byte[] signature = _hasher.ComputeHash(new ASCIIEncoding().GetBytes(fullSignatureData ?? string.empty);
string encodedSignature = Convert.ToBase64String(signature);

Now that we have the signature, we can add that to our previous dictionary of data

data.Add("oauth_signature", encodedSignature);

Generate the Header

With all of our OAuth data now present, we can use that to generate the OAuth header by joining all "oauth" keys alphabetically into a single string.

string oauthHeader = "OAuth " + string.join(
  ", ",
  data
    .Where(kvp => kvp.Key.StartsWith("oauth_"))
    .Select(kvp => string.Format(
      "{0}=\"{1}\"",
      Uri.EscapeDataString(kvp.Key),
      Uri.EscapeDataString(kvp.Value)
    ))
    .OrderBy(s => s)
);

Build the Request

After building the authentication header, we can actually build the request that will be sent to X / Twitter.

object bodyContent = new { text = "Hello World" };
StringContent body = new(
  JsonConvert.SerializeObject(bodyContent),
  Encoding.UTF8,
  "application/json"
);

HttpRequestMessage request = new(HttpMethod.Post, fullUrl);
request.Headers.Add("Authorization", oauthHeader);
request.Content = body;

Send the Request

Finally, we can make the request and handle the result accordingly

HttpResponse response = await _httpClient.SendAsync(request);

string responseBody = await response.Content.ReadAsStringAsync();
if(response.IsSuccessStatusCode)
{
  // Handle Success
}
else 
{
  // Handle Error
}

Kevin Williams

Springfield, Missouri
A full stack software engineer since 2018, specializing in Azure and .Net.