User GET call works, but POST does not

oauth

#1

I have been working on building a C# class structure to handle Twitter’s API calls for logging in and posting a tweet for a user. A copy of the code is below. Everything works as expected, I am able to obtain a request token, direct the user to the login page, then obtain the users access token. Everything in that section works as expected and well. The trouble begins when I start to use the access token. I am able to use any Twitter endpoint that is a “GET”, IE https://api.twitter.com/1.1/statuses/user_timeline.json, without any issues. Anytime I hit a “POST”, IE https://api.twitter.com/1.1/statuses/update.json, endpoint I get the Error 32. “Could not authenticate you.”

I am at a complete loss. Both the OAuth handshake and the calls with the user access token are using the same functions to build and request the data.

`

private string run_query(Dictionary<string,string> query_params, string url, string method, string token_name, bool multipart_form_submission = false)
{
    string auth_header = this.build_auth_header(method.ToUpper(), url, ((multipart_form_submission) ? null : query_params), token_name, multipart_form_submission);try
    {
        string request_string = "",
            body_string = "";
        if (query_params != null) {
            foreach (KeyValuePair<string, string> p in query_params) {
                request_string += ((!String.IsNullOrEmpty(request_string)) ? "&" : "") + UpperCaseUrlEncode(p.Key) + "=" + UpperCaseUrlEncode(p.Value);
                body_string += ((!String.IsNullOrEmpty(body_string)) ? "&" : "") + UpperCaseUrlEncode(p.Key) + "=" + UpperCaseUrlEncode(p.Value);
            }
            if (!String.IsNullOrEmpty(request_string)) request_string = "?" + request_string;
        }

        System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url + ((!multipart_form_submission) ? request_string : ""));
        request.Headers.Add("Authorization", auth_header);
        request.Method = method.ToUpper();
        if (multipart_form_submission) {
            request.SendChunked = true;
            request.TransferEncoding = "base64";
        }
        request.ContentType = (multipart_form_submission) ? "application/octet-stream" : "application/x-www-form-urlencoded";

        if (!String.IsNullOrEmpty(body_string) && String.Compare(method.ToLower(), "get") != 0) {
            try
            {
                System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
                byte[] body_array = encoding.GetBytes(body_string);
                request.ContentLength = body_array.Length;
                using (System.IO.Stream newStream = request.GetRequestStream())
                {
                    newStream.Write(body_array, 0, body_array.Length);
                }
            }
            catch (Exception) { }
        }

        System.Net.WebResponse response = request.GetResponse();

        return this.read_stream_to_string(response.GetResponseStream());
    }
    catch (System.Net.WebException ex)
    {
        return this.read_stream_to_string(ex.Response.GetResponseStream());
    }
}

private string build_auth_header(string method, string baseURL, Dictionary<string,string> extraParams, string token_name, bool multipart_form_submission) {
    this.header_params = new Dictionary<string, string>
    {
        { "oauth_consumer_key", this.consumer_key },
        { "oauth_nonce", this.generate_nonce() },
        { "oauth_signature_method", "HMAC-SHA1" },
        { "oauth_timestamp", ((Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds).ToString() },
        { "oauth_version", "1.0" },
        { "oauth_token", ((this.tokens != null && this.tokens.ContainsKey(token_name)) ? this.tokens[token_name].token : "") }
    };

    if (String.IsNullOrEmpty(token_name)) this.header_params.Add("oauth_callback", this.callback_url);

    if (extraParams != null) {
        foreach (KeyValuePair<string, string> p in extraParams) {
            if(!String.IsNullOrEmpty(p.Value)) this.header_params.Add(p.Key, p.Value);
        }
    }

    List<string> sorted_keys = this.header_params.Keys.ToList();
    sorted_keys.Sort();

    string param_str = "";

    foreach (string key in sorted_keys) {
        string value = this.header_params[key];
        if (!String.IsNullOrEmpty(value)) param_str += ((param_str.Length > 0) ? "&" : "") + this.UpperCaseUrlEncode(key) + "=" + this.UpperCaseUrlEncode(value);
    }

    string 
        base_string = (multipart_form_submission) ? this.UpperCaseUrlEncode(baseURL) : method.ToUpper() + "&" + this.UpperCaseUrlEncode(baseURL) + "&" + this.UpperCaseUrlEncode(param_str),
        crypt_key = this.UpperCaseUrlEncode(this.consumer_secret) + "&" + ((this.tokens.Count > 0 && this.tokens.ContainsKey(token_name)) ? this.UpperCaseUrlEncode(this.tokens[token_name].secret) : ""),
        hash = "";

    using (var hmac = new HMACSHA1(System.Text.Encoding.ASCII.GetBytes(crypt_key)))
    {
        byte[] crypt_hash = hmac.ComputeHash(System.Text.Encoding.ASCII.GetBytes(base_string));
        hash = Convert.ToBase64String(crypt_hash);
    }

    this.header_params.Add("oauth_signature", hash);
    
    sorted_keys = this.header_params.Keys.ToList();
    sorted_keys.Sort();

    param_str = "";

    foreach (string key in sorted_keys) {
        string value = this.header_params[key];
        if (!String.IsNullOrEmpty(value)) {
            if (extraParams == null || !extraParams.ContainsKey(key)) param_str += ((param_str.Length > 0) ? ", " : "") + this.UpperCaseUrlEncode(key) + "=\"" + this.UpperCaseUrlEncode(value) + "\"";
        }
    }

    return "OAuth " + param_str;
}`

#2

Does your Twitter application have permission to write updates on behalf of the user? Visit apps.twitter.com, click on your app, select the Permissions tab, and make sure your app is requesting Read and Write permissions.

If your app was issued a user token while configured for read-only you should remove the previous user token and go through the token grant process again. You may revoke access to your application(s) from Twitter’s settings.
https://twitter.com/settings/applications


#3

I finally figured it out. My application has read/write access and the users token was also set to read/write. My issue was in the signing of the request. When percent encoding the extra parameters, IE the status, my percent encoding function was placing “+” for spaces instead of “%20”. So my signature was invalid.

I was using the method HttpUtility.UrlEncode, I switched to Uri.EscapeUriString and everything worked properly.