Search code examples
pythontwitterpython-requestsmicropython

Twitter API Post Request -- Error Code 215 Bad Authentication data


I am building a Twitter bot in MicroPython to run on a NodeMCU ESP8266 board. MicroPython doesn't have support for OAuth 1.0 requests out of the box, so I had to roll my own. I've been following these write-ups to build my program:

  1. https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/authorizing-a-request
  2. https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/creating-a-signature

When ever I send a POST request to send a tweet, I get the following error response: {"errors":[{"code":215,"message":"Bad Authentication data."}]}.

I wrote a small wrapper class for the MicroPython urequests module, called oauth_requests

import urequests as requests

class oauth_request:
    @classmethod
    def post(cls, url, params, key_ring):
        """ Post method with OAuth 1.0
            Args:
                url (str): URL to send request to.
                params (dict): Params to append to URL.
                key_ring (dict): Dictionary with API keys.
            Returns:
                Response from POST request.
        """
        auth_header = cls.__create_auth_header("POST", url, params, **key_ring)
        headers = {"Authorization": auth_header}
        url += "?{}".format(
            "&".join([
                "{}={}".format(cls.__percent_encode(str(k)), cls.__percent_encode(str(v)))
                for k, v in params.items()
            ]))
        return requests.post(url, headers=headers)

The return value of cls.__create_auth_header(...) is an "OAuth" string, like the one at the end of link #1 above. I have validated that my implementation of the HMAC-SHA1 algorithm produces the same output from the sample data in link #2 up above. I was able to send the same response through PostMan, so my API keys are valid.

Am I incorrectly creating the request headers correctly?

I have committed my code to this repo.


Solution

  • I ended up finding the fix for my problem. The main problem was I wasn't percent encoding my value for oauth_signature. However, even after that I was getting a new error, {"errors":[{"code":32,"message":"Could not authenticate you."}]}.

    From the link I originally posted up above about creating the signature, you build the base parameter string from percent-encoding, and "&" joining an oauth dictionary like this:

    url_params = {
        "status": "Tweeting from the future."
    }
    
    oauth = {
        "include_entities": "true",
        "oauth_consumer_key": consumer_key,
        "oauth_nonce": generate_nonce(),
        "oauth_signature_method": "HMAC-SHA1",
        "oauth_timestamp": 946684800 + time.time(),
        "oauth_token": access_token,
        "oauth_version": 1.0,
    }
    
    oauth.update(url_params)
    
    base_string = percent_encode_and_join(oauth)
    

    (The time value is odd because the micropython system time epoch starts in Jan 1st, 2000 instead of Jan 1st, 1970)

    However when I was debugging the request using PostMan it was working. I realized that Postman couldn't know to add that include_entities entry when it is calculating the signature. Lo and behold when I removed that key from this dictionary, the error went away.

    Refer to my repo up above for the code.