Twitter OAuth Request Token returning Unauthorized

Viewed 662

I'm trying to get a request token from Twitter OAuth, but I keep getting 401 Unauthorized, but I have no idea why. I have tried to follow the signature creation as good as I can and was struggling with 400 Bad Request for a while but finally cracked the code to get a valid request only to be met by the 401.

I feel like I'm missing something really simple but I just can't find out what.

Here's the code I'm using:

using (HttpClient client = new HttpClient())
{
    client.BaseAddress = new Uri("https://api.twitter.com/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

    string oauth_nonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));
    string oauth_callback = Uri.EscapeUriString("oob");
    string oauth_signature_method = "HMAC-SHA1";
    string oauth_timestamp = Convert.ToInt64(ts.TotalSeconds).ToString();
    string oauth_consumer_key = OAUTH_KEY;
    string oauth_version = "1.0";

    // Generate signature
    string baseString = "POST&";
    baseString += Uri.EscapeDataString(client.BaseAddress + "oauth/request_token") + "&";
    // Each oauth value sorted alphabetically
    baseString += Uri.EscapeDataString("oauth_callback=" + oauth_callback + "&");
    baseString += Uri.EscapeDataString("oauth_consumer_key=" + oauth_consumer_key + "&");
    baseString += Uri.EscapeDataString("oauth_nonce=" + oauth_nonce + "&");
    baseString += Uri.EscapeDataString("oauth_signature_method=" + oauth_signature_method + "&");
    baseString += Uri.EscapeDataString("oauth_timestamp=" + oauth_timestamp + "&");
    baseString += Uri.EscapeDataString("oauth_version=" + oauth_version + "&");

    string signingKey = Uri.EscapeDataString(OAUTH_SECRET) + "&";
    HMACSHA1 hasher = new HMACSHA1(new ASCIIEncoding().GetBytes(signingKey));
    string oauth_signature = Convert.ToBase64String(hasher.ComputeHash(new ASCIIEncoding().GetBytes(baseString)));

    client.DefaultRequestHeaders.Add("Authorization", "OAuth " +
        "oauth_nonce=\"" + oauth_nonce + "\"," +
        "oauth_callback=\"" + oauth_callback + "\"," +
        "oauth_signature_method=\"" + oauth_signature_method + "\"," +
        "oauth_timestamp=\"" + oauth_timestamp + "\"," +
        "oauth_consumer_key=\"" + oauth_consumer_key + "\"," +
        "oauth_signature=\"" + Uri.EscapeDataString(oauth_signature) + "\"," +
        "oauth_version=\"" + oauth_version + "\""
    );

    HttpResponseMessage response = await client.PostAsJsonAsync("oauth/request_token", "");
    var responseString = await response.Content.ReadAsStringAsync();
}

I'm using .Net 4.5 so the Uri.EscapeDataString() should give the correct percent encoded string.

EDIT

I noticed that the oauth_nonce value sometimes included non-alphanumeric characters so I changed the variable calculation to the following:

string nonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));
string oauth_nonce = new string(nonce.Where(c => Char.IsLetter(c) || Char.IsDigit(c)).ToArray());

This unfortunately didn't help.

I've also tried Uri.EscapeUriData() for all the values in the Authorization header, as well as adding a space after each comma (as the documentation on Twitter says so), but that didn't help either.

2 Answers

Thank you @GTHvidsten for your example.

By digging into your working second example, I discovered the issue I had with my code, and it's probably the same reason your original example wasn't working.

It's not enough to encode the individual request parameter key/value pairs that constitute the signature base string, but apparently you have to also encode the entire joined key/value request parameter string, which will thus encode the '=' signs. In your second example, this line of code does just that: baseString += Uri.EscapeDataString(parameterCollection);

In your original example, there is no code that encodes the request parameters.

Related