How to debug 401 error in Twitter OAuth?


#1

Hi, I’m trying to implement sign in via Twitter button on my website using Python, following this flow: https://dev.twitter.com/web/sign-in/implementing.

The problem is that I’m getting 401 error no matter what upon the very first request to https://api.twitter.com/oauth/request_token endpoint.

I have the button “Allow this application to sign in with Twitter” enabled:

I also tried to debug this in OAuth Tool by getting a raw curl request from its signature-generator, but there seems to be a flaw in OAuth Tool, cause it would always include “oauth_token” parameter and calculate the signature with it, which is wrong, cause “request_token” endpoint shouldn’t accept this parameter.

So, how do I debug it?

Just in case, here is my source code for Django:

def twitter_oauth_submit_button_view(request):

    TWITTER_CLIENT_SECRET = settings.TWITTER_CLIENT_SECRET
    TWITTER_CLIENT_ID = settings.TWITTER_CLIENT_SECRET

    urlencode = urllib.quote_plus

    nonce = make_nonce()
    timestamp = int(time.time())
    # encode with client secret, NOTE the ampersand sign added to secret! Ampersand comes from the fact, that encoding key
    # should normally be "TWITTER_CLIENT_SECRET&OAUTH_TOKEN_SECRET", but as we don't have OAUTH_TOKEN_SECRET yet, we should
    # use just "TWITTER_CLIENT_SECRET&".
    # also note the urlencode() around TWITTER_CLIENT_SECRET

    hmaced = hmac.new(urlencode(TWITTER_CLIENT_SECRET)+"&", make_basestring(nonce=nonce, timestamp=timestamp), sha1)
    signature = hmaced.digest().encode("base64").rstrip("\n")

    oauth_header = ('OAuth ' + 
                   'oauth_callback="' + urlencode('http://sabotage.toolly.ru/twitter_oauth2callback') + '", ' +
                   'oauth_consumer_key="' + urlencode(TWITTER_CLIENT_SECRET) + '", ' +                   
                   'oauth_nonce="' + urlencode(nonce) + '", ' +
                   'oauth_signature="' + urlencode(signature) + '", ' +
                   'oauth_signature_method="' + urlencode('HMAC-SHA1') + '", ' +
                   'oauth_timestamp="' + urlencode(str(timestamp)) + '", ' +
                   'oauth_version="1.0"')

    headers = {'Authorization': oauth_header}
    r = requests.post("https://api.twitter.com/oauth/request_token", headers=headers, data={})
    return HttpResponseRedirect("https://api.twitter.com/oauth/authenticate?oauth_token=%s" % r.data["auth_token"])

def make_basestring(nonce, timestamp, request_type="POST", url="https://api.twitter.com/oauth/request_token"):
    urlencode = urllib.quote_plus

    params = [("oauth_callback", 'http://sabotage.toolly.ru/twitter_oauth2callback'),
              ("oauth_consumer_key", settings.TWITTER_CLIENT_ID),
              ("oauth_nonce", nonce),
              ("oauth_signature_method", "HMAC-SHA1"),
              ("oauth_timestamp", str(timestamp)),
              ("oauth_version", "1.0")]
    oauth_params_string = "&".join([(urlencode(key) + "=" + urlencode(value)) for key, value in params])
    base_string = urlencode(request_type) + "&" + urlencode(url) + "&" + urlencode(oauth_params_string)
    return base_string

def make_nonce(length=32, charset='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
    return "".join([choice(charset) for i in range(length)])

My basestring is:

POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttp%253A%252F%252Fsabotage.toolly.ru%252Ftwitter_oauth2callback%26oauth_consumer_key%3DmBDDk6yXAX5EbF1R3NLwAXPRe%26oauth_nonce%3D6EzEITZAfuVEPaVjIBMK3GV54K18Gk3k%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1417604435%26oauth_version%3D1.0

My Authentication header is:

OAuth oauth_callback="http%3A%2F%2Fsabotage.toolly.ru%2Ftwitter_oauth2callback", oauth_consumer_key="7HoVWmsICqmRAGQJCym4OESn6qbhZm9XCvg9AjRcVKM3MG71s1", oauth_nonce="6EzEITZAfuVEPaVjIBMK3GV54K18Gk3k", oauth_signature="JLLLrJ75SUsfxjTqBsIQCGaZkRM%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1417604435", oauth_version="1.0"

#2

Sorry, guys! After 3 days of pain, I found the error in the very first lines of code: I’m sending TWITTER_CLIENT_SECRET instead of TWITTER_CLIENT_ID and, moreover, mistakingly assigned TWITTER_CLIENT_ID to the value of settings.TWITTER_CLIENT_SECRET. Now it works!

Still, I think, Twitter devs could make an option in Twitter OAuth Tool signature generator not to send the OAUTH_TOKEN with request, cause now it’s impossible to troubleshoot this first step with it.