Search code examples
pythonpostoauthhttplib2fitbit

Post request failing with python httplib2, works with curl


I'm trying to request access tokens from the fitbit API, but it keeps returning 401 Unauthorized status, even though I configure the request identical to a corresponding curl query - which succeeds. The error message returned says: "errorType":"invalid_client","message":"Invalid authorization header format. Is there some nuance of how httplib2 builds its requests that is throwing me off here?...

(Working) curl query:

curl    -X POST -i 
-H 'Authorization: Basic <LONG_CODE>'
-H 'Content-Type: application/x-www-form-urlencoded' 
-d "clientId=<CLIENT_ID>" 
-d "grant_type=authorization_code" 
-d "redirect_uri=http%3A%2F%2F127.0.0.1%3A5000%2Ffitbit-callback" 
-d "code=<AUTHORIZATION_GRANT_CODE>" 
https://api.fitbit.com/oauth2/token

Non-working python request (edited):

TOKEN_URL = 'https://api.fitbit.com'/oauth2/token'
CALLBACK_URI  = 'http://127.0.0.1:5000/fitbit-callback'

auth_header = base64.b64encode(bytes(<CLIENT_ID> + ':' + <CLIENT_SECRET>, 'utf-8'))
headers = {
    'Authorization': 'Basic %s' % auth_header,
    'Content-Type' : 'application/x-www-form-urlencoded'
    }

params = {
    'client_id': <CLIENT_ID>,
    'grant_type': 'authorization_code',
    'redirect_uri': CALLBACK_URI,
    'code': <AUTHORIZATION_GRANT_CODE>
    }
urlparams = urlencode(params)

resp, content = h.request(TOKEN_URL, 
    'POST', 
    urlparams,
    headers)

Not evident from code:

  • the auth_header-variable in python matches <LONG_CODE>

Terminal response after python3 fitbit.py:

send: b"POST /oauth2/token HTTP/1.1\r\nHost: api.fitbit.com\r\nContent-Length: 153\r\nauthorization: Basic b'<LONG_CODE>'\r\ncontent-type: application/x-www-form-urlencoded\r\nuser-agent: Python-httplib2/0.10.3 (gzip)\r\naccept-encoding: gzip, deflate\r\n\r\n"
send: b'client_id=<CLIENT_ID>&grant_type=authorization_code&redirect_uri=http%3A%2F%2F127.0.0.1%3A5000%2Ffitbit-callback&code=<AUTHORIZATION_GRANT_CODE>'
reply: 'HTTP/1.1 401 Unauthorized\r\n'
header: Date header: Content-Type header: Transfer-Encoding header: Connection header: Cache-control header: WWW-Authenticate header: Content-Language header: Content-Encoding header: Vary header: X-Frame-Options header: Server header: CF-RAY 

Running print(content):

b'{"errors":[{"errorType":"invalid_client","message":"Invalid authorization header format. Visit https://dev.fitbit.com/docs/oauth2 for more information on the Fitbit Web API authorization process."}],"success":false}'

Solution

  • Well this is awkward. I didn't notice it before I directed the requests towards a request capture service that let me analyze them (like runscope), but I seem to simply have missed the b'<LONG_CODE>' formatting in the python example. This fixed it:

    auth_header.decode("utf-8")

    Maybe this question should be deleted, if it is unlikely to help anyone else...