Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using C# to create the Twitter authorization header for search

Tags:

c#

twitter

I'm writing to write a C# method to generate a authentication header for Twitter. I'm trying to search twitter through this API: https://api.twitter.com/1.1/search/tweets.json.

Here's the URL I call:

https://api.twitter.com/1.1/search/tweets.json?q=%23countryman+OR+%23johncooperworks+OR+%40mini%26since_id%3d24012619984051000%26max_id%3d250126199840518145%26result_type%3dmixed%26count%3d4

Here's my method:

private string GetTwitterAuthHeader()
{
    const string oauthConsumerKey = "";
    const string oauthConsumerSecret = "";
    const string oauthToken = "";
    const string oauthTokenSecret = "";
    const string oauthVersion = "1.0";
    const string oauthSignatureMethod = "HMAC-SHA1";

    var oauthNonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString(CultureInfo.InvariantCulture)));
    var timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
    var oauthTimestamp = Convert.ToInt64(timeSpan.TotalSeconds).ToString(CultureInfo.InvariantCulture);

    const string resourceUrl = "https://api.twitter.com/1.1/search/tweets.json";
    const string baseFormat = "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method={2}" +
                                "&oauth_timestamp={3}&oauth_token={4}&oauth_version={5}";

    var baseString = string.Format(baseFormat,
                                oauthConsumerKey,
                                oauthNonce,
                                oauthSignatureMethod,
                                oauthTimestamp,
                                oauthToken,
                                oauthVersion
                                );

    baseString = string.Concat("GET&", Uri.EscapeDataString(resourceUrl), "&", Uri.EscapeDataString(baseString));

    var compositeKey = string.Concat(Uri.EscapeDataString(oauthConsumerSecret),
                            "&", Uri.EscapeDataString(oauthTokenSecret));

    string oauthSignature;
    using (var hasher = new HMACSHA1(Encoding.ASCII.GetBytes(compositeKey)))
    {
        oauthSignature = Convert.ToBase64String(
            hasher.ComputeHash(Encoding.ASCII.GetBytes(baseString)));
    }

    const string headerFormat = "OAuth oauth_consumer_key=\"{0}\", " +
                                "oauth_nonce=\"{1}\", " +
                                "oauth_signature=\"{2}\", " +
                                "oauth_signature_method=\"{3}\", " +
                                "oauth_timestamp=\"{4}\", " +
                                "oauth_token=\"{5}\", " +
                                "oauth_version=\"{6}\"";

    var authHeader = string.Format(headerFormat,
                            Uri.EscapeDataString(oauthConsumerKey),
                            Uri.EscapeDataString(oauthNonce),
                            Uri.EscapeDataString(oauthSignature),
                            Uri.EscapeDataString(oauthSignatureMethod),
                            Uri.EscapeDataString(oauthTimestamp),
                            Uri.EscapeDataString(oauthToken),
                            Uri.EscapeDataString(oauthVersion)
                    );

    return authHeader;
}

The error I get is:

{
    "errors": [
        {
            "message": "Bad Authentication data",
            "code": 215
        }
    ]
}

Any pointers?

Do I need to account for the actual search query in generating the auth header? (e.g. the values I append to the search API)?

I'm finding it hard to debug.

Thanks!

EDIT:

Based on feedback, here's an update:

var resourceUrl = "https://api.twitter.com/1.1/search/tweets.json";
const string baseFormat = "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method={2}" +
                            "&oauth_timestamp={3}&oauth_token={4}&oauth_version={5}&q={6}";

var baseString = string.Format(baseFormat,
                            oauthConsumerKey,
                            oauthNonce,
                            oauthSignatureMethod,
                            oauthTimestamp,
                            oauthToken,
                            oauthVersion,
                            query
                            );

baseString = string.Concat("GET&", 
    Uri.EscapeDataString(resourceUrl), "&", 
    Uri.EscapeDataString(baseString));

Having read https://dev.twitter.com/oauth/overview/creating-signatures closer, this seems to be right. Still, I get the same error.

like image 577
Wade Avatar asked Sep 10 '14 17:09

Wade


People also ask

What is C in used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

How do I use C on my computer?

It is a bit more cryptic in its style than some other languages, but you get beyond that fairly quickly. C is what is called a compiled language. This means that once you write your C program, you must run it through a C compiler to turn your program into an executable that the computer can run (execute).

Is C used nowadays?

C exists everywhere in the modern world. A lot of applications, including Microsoft Windows, run on C. Even Python, one of the most popular languages, was built on C. Modern applications add new features implemented using high-level languages, but a lot of their existing functionalities use C.

Why do people use C?

The biggest advantage of using C is that it forms the basis for all other programming languages. The mid-level language provides the building blocks of Python, Java, and C++. It's a fundamental programming language that will make it easier for you to learn all other programming languages.


2 Answers

Wrap your "query" in Uri.EscapeDataString().

Your exact code above works for me if I do that. That said, you should really be escaping all of those parameter keys and values, and I'm a bit stumped as to why the nonce is working without escaping, since it's base64 encoded. But perhaps I was just lucky in my testing and never hit a nonce with a slash in it.

like image 133
user94559 Avatar answered Oct 21 '22 09:10

user94559


So, I wrote a class that does this. Let's see how well I can break it down. Let's start with the entire class.

    public class TwitterAuthTool : IDisposable {
    private const string BASE_AUTH_URL = "https://api.twitter.com/oauth2/token";
    private const string BASE_SEARCH_URL = "https://api.twitter.com/1.1/search/tweets.json";
    private const string BASE_INVALIDATE_URL = "https://api.twitter.com/oauth2/invalidate_token";

    private AccessToken Credentials;
    private string BearerTokenCredentials;

    public TwitterAuthTool( string p_ConsumerKey, string p_ConsumerSecret ) {
        BearerTokenCredentials =
            Convert.ToBase64String(
                Encoding.ASCII.GetBytes(
                    string.Format( "{0}:{1}",
                        Uri.EscapeUriString( p_ConsumerKey ),
                        Uri.EscapeUriString( p_ConsumerSecret ) ) ) );

        HttpWebRequest _Request = HttpWebRequest.Create( BASE_AUTH_URL ) as HttpWebRequest;
        _Request.KeepAlive = false;
        _Request.Method = "POST";
        _Request.Headers.Add( "Authorization", string.Format( "Basic {0}", BearerTokenCredentials ) );
        _Request.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";

        byte[] _Content = Encoding.ASCII.GetBytes( "grant_type=client_credentials" );
        using( Stream _Stream = _Request.GetRequestStream() )
            _Stream.Write( _Content, 0, _Content.Length );

        HttpWebResponse _Response = _Request.GetResponse() as HttpWebResponse;

        DataContractJsonSerializer _AccessTokenJsonSerializer = new DataContractJsonSerializer( typeof( AccessToken ) );
        Credentials = (AccessToken)_AccessTokenJsonSerializer.ReadObject( _Response.GetResponseStream() );
    }

    public List<Tweet> GetLatest( string p_Query, int p_Count = 100 ) {
        TwitterResults _TwitterResults;
        List<Tweet> _ReturnValue = new List<Tweet>();
        DataContractJsonSerializer _JsonSerializer = new DataContractJsonSerializer( typeof( TwitterResults ) );

        HttpWebRequest _Request = WebRequest.Create( string.Format( "{0}?q={1}&result_type=recent&count={2}", BASE_SEARCH_URL, p_Query, p_Count ) ) as HttpWebRequest;
        _Request.Headers.Add( "Authorization", string.Format( "Bearer {0}", Credentials.access_token ) );
        _Request.KeepAlive = false;
        _Request.Method = "GET";

        HttpWebResponse _Response = _Request.GetResponse() as HttpWebResponse;
        _TwitterResults = (TwitterResults)_JsonSerializer.ReadObject( _Response.GetResponseStream() );
        _ReturnValue.AddRange( _TwitterResults.statuses );

        while( !string.IsNullOrWhiteSpace( _TwitterResults.search_metadata.next_results ) ) {
            _Request = WebRequest.Create( string.Format( "{0}{1}", BASE_SEARCH_URL, _TwitterResults.search_metadata.next_results ) ) as HttpWebRequest;
            _Request.Headers.Add( "Authorization", string.Format( "Bearer {0}", Credentials.access_token ) );
            _Request.KeepAlive = false;
            _Request.Method = "GET";

            _Response = _Request.GetResponse() as HttpWebResponse;
            _TwitterResults = (TwitterResults)_JsonSerializer.ReadObject( _Response.GetResponseStream() );
            _ReturnValue.AddRange( _TwitterResults.statuses );
        }

        return _ReturnValue;
    }

    public List<Tweet> GetLatestSince( string p_Query, long p_SinceId, int p_Count = 100 ) {
        TwitterResults _TwitterResults;
        List<Tweet> _ReturnValue = new List<Tweet>();
        DataContractJsonSerializer _JsonSerializer = new DataContractJsonSerializer( typeof( TwitterResults ) );

        HttpWebRequest _Request = WebRequest.Create( string.Format( "{0}?q={1}&result_type=recent&count={2}&since_id={3}", BASE_SEARCH_URL, p_Query, p_Count, p_SinceId ) ) as HttpWebRequest;
        _Request.Headers.Add( "Authorization", string.Format( "Bearer {0}", Credentials.access_token ) );
        _Request.KeepAlive = false;
        _Request.Method = "GET";

        HttpWebResponse _Response = _Request.GetResponse() as HttpWebResponse;
        _TwitterResults = (TwitterResults)_JsonSerializer.ReadObject( _Response.GetResponseStream() );
        _ReturnValue.AddRange( _TwitterResults.statuses );

        while( !string.IsNullOrWhiteSpace( _TwitterResults.search_metadata.next_results ) ) {
            _Request = WebRequest.Create( string.Format( "{0}{1}", BASE_SEARCH_URL, _TwitterResults.search_metadata.next_results ) ) as HttpWebRequest;
            _Request.Headers.Add( "Authorization", string.Format( "Bearer {0}", Credentials.access_token ) );
            _Request.KeepAlive = false;
            _Request.Method = "GET";

            _Response = _Request.GetResponse() as HttpWebResponse;
            _TwitterResults = (TwitterResults)_JsonSerializer.ReadObject( _Response.GetResponseStream() );
            _ReturnValue.AddRange( _TwitterResults.statuses );
        }

        return _ReturnValue;
    }

    public void Dispose() {
        HttpWebRequest _Request = WebRequest.Create( BASE_INVALIDATE_URL ) as HttpWebRequest;
        _Request.KeepAlive = false;
        _Request.Method = "POST";
        _Request.Headers.Add( "Authorization", string.Format( "Basic {0}", BearerTokenCredentials ) );
        _Request.ContentType = "application/x-www-form-urlencoded";

        byte[] _Content = Encoding.ASCII.GetBytes( string.Format( "access_token={0}", Credentials.access_token ) );
        using( Stream _Stream = _Request.GetRequestStream() )
            _Stream.Write( _Content, 0, _Content.Length );

        try {
            _Request.GetResponse();
        } catch {
            // The bearer token will time out if this fails.
        }
    }
}

Twitter uses something OAuth2-ish. You have to login before you do ANYTHING else. This is what the constructor of the above class does. When you do that, you get back an access token. It's a simple little JSON object that easily deserializes into the following little class.

    [DataContract]
public class AccessToken {
    [DataMember]
    public string token_type;

    [DataMember]
    public string access_token;
}

Once you have the correct credentials, you use THOSE to access things. Unfortunately, the JSON objects you get back from a query are huge. I had to create 17 different classes to deserialize the entirety of it. I'll look at putting that library up on github.

As you see in the Dispose, I don't care if the auth token fails. The credentials they give you time out after 8 hours.

like image 41
Chris Gardner Avatar answered Oct 21 '22 09:10

Chris Gardner