Search code examples
c#twitteroauth

Twitter OAuth Request Token returning Unauthorized


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.


Solution

  • I started from scratch and tried a slightly different approach, but it should've yielded the same result. However, it didn't and instead works as intended!

    If anybody can point of the practical difference between this working code and the code in the original question, I would really appreciate it, because to my eyes they produce identical results.

    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 timestamp = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        string nonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));
    
        Dictionary<string, string> oauth = new Dictionary<string, string>
        {
            ["oauth_nonce"] = new string(nonce.Where(c => char.IsLetter(c) || char.IsDigit(c)).ToArray()),
            ["oauth_callback"] = "http://my.callback.url",
            ["oauth_signature_method"] = "HMAC-SHA1",
            ["oauth_timestamp"] = Convert.ToInt64(timestamp.TotalSeconds).ToString(),
            ["oauth_consumer_key"] = OAUTH_KEY,
            ["oauth_version"] = "1.0"
        };
    
        string[] parameterCollectionValues = oauth.Select(parameter =>
                Uri.EscapeDataString(parameter.Key) + "=" +
                Uri.EscapeDataString(parameter.Value))
            .OrderBy(kv => kv)
            .ToArray();
        string parameterCollection = string.Join("&", parameterCollectionValues);
    
        string baseString = "POST";
        baseString += "&";
        baseString += Uri.EscapeDataString(client.BaseAddress + "oauth/request_token");
        baseString += "&";
        baseString += Uri.EscapeDataString(parameterCollection);
    
        string signingKey = Uri.EscapeDataString(OAUTH_SECRET);
        signingKey += "&";
        HMACSHA1 hasher = new HMACSHA1(new ASCIIEncoding().GetBytes(signingKey));
        oauth["oauth_signature"] = Convert.ToBase64String(hasher.ComputeHash(new ASCIIEncoding().GetBytes(baseString)));
    
        string headerString = "OAuth ";
        string[] headerStringValues = oauth.Select(parameter =>
                Uri.EscapeDataString(parameter.Key) + "=" + "\"" +
                Uri.EscapeDataString(parameter.Value) + "\"")
            .ToArray();
        headerString += string.Join(", ", headerStringValues);
    
        client.DefaultRequestHeaders.Add("Authorization", headerString);
    
        HttpResponseMessage response = await client.PostAsJsonAsync("oauth/request_token", "");
        var responseString = await response.Content.ReadAsStringAsync();
    }