Problema REST API 1.1 Twitter

Nucklear

He estado preguntando por ahí y nadie me ha sabido dar respuesta a esto, pero lo dejo por aqui a ver si suena la flauta.

Estoy intentando conectar con el nuevo API de Twitter enviando la request con urllib2 de python como manda la doc. Pero siempre recibo un error 403 como respuesta.

Este es el código de la función:

def auth_API():
    url = 'https://api.twitter.com/oauth2/token'
    header = {}
    values = {}
    header['User-Agent'] = 'Mozilla/6.0 (Windows NT 6.2; WOW64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1'
    header['Authorization'] = 'Basic ' + B64BEARERTOKENCREDS
    header['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
    header['Accept-Encoding'] = 'gzip'
    values['grant_type'] = 'client_credentials'

data = urllib.urlencode(values)
req = urllib2.Request(url, data, header)
try:
    response = urllib2.urlopen(req)
    response.read()
except urllib2.HTTPError as e:
    print e

Según la documentación la request tiene que tener esta estructura:

POST /oauth2/token HTTP/1.1
Host: api.twitter.com
User-Agent: My Twitter App v1.0.23
Authorization: Basic NnB1[...]9JM29jYTNFOA==
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 29
Accept-Encoding: gzip

grant_type=client_credentials

Y esta es mi request tras ejecutar el código:

POST /oauth2/token HTTP/1.1
Content-Length: 29
Accept-Encoding: gzip
Connection: close
User-Agent: Mozilla/6.0 (Windows NT 6.2; WOW64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1
Host: api.twitter.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Authorization: Basic NnB1[...]YTNFOA==

grant_type=client_credentials

¿Alguna idea de que puede estar fallando?

PD: Si usais stackoverflow he iniciado una recompensa así que si tenéis la respuesta os podéis llevar la reputación que doy.

elkaoD

#1 lee la respuesta en la excepción, seguro que te viene info extra. Me sigue sonando a que estás generando mal el código de autenticación.

1 respuesta
Nucklear

#2 Esta es la parte donde genero lo que pide:

CONSUMERKEY = "6pu[...]69A"
CONSUMERSECRET = "yohrE1[...]OI3oca3E8"

# Derivated Constants

BEARERTOKENCREDS = CONSUMERKEY + ':' + CONSUMERSECRET
B64BEARERTOKENCREDS = base64.encodestring(BEARERTOKENCREDS)

Según esto es así:

Step 1: Encode consumer key and secret

The steps to encode an application's consumer key and secret into a set of credentials to obtain a bearer token are:

URL encode the consumer key and the consumer secret according to RFC 1738. Note that at the time of writing, this will not actually change the consumer key and secret, but this step should still be performed in case the format of those values changes in the future.
Concatenate the encoded consumer key, a colon character ":", and the encoded consumer secret into a single string.
Base64 encode the string from the previous step.

La respuesta del servidor es:

python auth.py    

HTTP Error 403: Forbidden   

None
1 respuesta
elkaoD

#3 esa no es la respuesta del server. Estás imprimiendo la excepción, pero no lees el payload.

1 respuesta
Nucklear

#4 ¿Te refieres a no capturar la excepción y leer todo el stack de esta?

1 respuesta
elkaoD

#5 igual que tú mandas "grant_type=client_credentials" como cuerpo del POST, el servidor te manda también contenido de vuelta (JSON si no recuerdo mal, con una cadena describiendo el error).

Ahí tendrás tu respuesta.

En cuanto a lo de generar mal, me refería más al proceso de generación de las credenciales OAuth que a cómo generas el código.

1 respuesta
Nucklear

#6 Vale, ya entiendo, pero el JSON que devuelve no pone mucho mas, era algo así como:

HTTP Error 403: Forbidden Authentication Failed/Unauthenticated

EDIT:

Según me dicen en el foro de twitter el problema es con la creación de los credenciales:
https://dev.twitter.com/discussions/17082

¿Alguien sería tan amable de generar unos suyos y ver que falla?, es que yo sigo los pasos al pie de la letra

2 respuestas
elkaoD

#7 tenía que poner algo más. Tiene pinta de que eso es la respuesta HTTP en modo texto, no el payload.

Tiene que ser algo del estilo de:

{"errors":[{"code":99,"label":"authenticity_token_error","message":"Unable to verify your credentials"}]}

¿Estás haciendo bien el paso previo de pillar la CONSUMERKEY y demás?

Es que si no ya no se me ocurre otra.

1 respuesta
Nucklear

#8 Haciendo la request a mano con Tamper Data este es el json que devuelve:

{"errors":[{"code":99,"label":"authenticity_token_error","message":"Unable to verify your credentials"}]}

Según lo que me dicen en el foro de twitter el código funciona con unos credenciales válidos, así que el problema está en como estoy generandolo, con lo puesto en #3.

¿Puede ser que el base64.encodestring() genere mal la cadena?

1 respuesta
elkaoD

#7 #9 hombre como no nos los generes tú no podemos crear credenciales para tu app.

¿A ver si va a ser ese el problema?

Sabes que CONSUMER* van ligadas a tu app, pero NO son las credenciales de tu app, ¿verdad? OAuth sirve para que un usuario te delegue su cuenta, pero tú tienes que crearle las credenciales (con su permiso) como cuando haces login con Google/OpenID y tal.

Para comprobar la cadena usa cualquiera de los decoders de base64 que hay online y mira que coincida, pero vamos, dudo que te la esté generando mal, muy raro sería.

1 respuesta
Nucklear

#10 A ver a ver que me aclare.

Yo tengo las consumer keys que me genera Twitter para mi app, según esto: https://dev.twitter.com/docs/auth/application-only-auth

Yo tengo que usar esas keys para generar un token que envío al servidor para que me envíe mi token con el que poder firmar cada una de las requests que haga ¿no?

Porque eso es lo que estoy haciendo...

1 respuesta
elkaoD

#11 ah, vale, no sabía que OAuth2 soportara autenticación por-app (me pasa por no leerme todo). ¿Has comprobado la cadena en un decoder online?

1 respuesta
Nucklear

#12 Si, y es lo que debería ser. A ver si me contestan en el soporte de Twitter y vuelvo por aqui, porque siento que es una chorrada de estas que hasta que caes de la burra no te enteras.

1 respuesta
elkaoD

#13 de http://docs.python.org/2/library/base64.html

encodestring() returns a string containing one or more lines of base64-encoded data always including an extra trailing newline ('\n' ).

A ver si va a ser eso...

Usa (comprueba que no la haya cagado porque voy de memorieta):

header['Authorization'] = 'Basic ' + B64BEARERTOKENCREDS[:-1]

EDIT: o sino prueba con base64.b64encode, o base64.standard_b64encode.

Ya nos dirás.

Nucklear

Vale algo pasa con encodestring() porque hardcodeando el codigo funciona, ese tiene que ser el problema. Voy a ver si la respuesta es válida

Nucklear

Bueno, despues de probar con base64.b64encode funciona correctamente, ahora tengo otro pequeño problemilla.

Despues de hacer:

response = urllib2.urlopen(req)

return response

response es un objeto que devuelve urllib, pero utilizando response.read() lo que devuelve es:

"▼ ♥½VJLNN-.Ä/╔¤N═S▓Rr─♠╝|Ê☻í╠³─♀_7cU#7▼O╦,úÓTºt╦(#▼▼¾¬░(á`╣¬▒KUÿkàSfbzEahYJZVeR║╗╣OFHHHiPRvYZêùIÉüiüw¿ÆÄ↕ÏÍ°Æ╩éTáıI®ëE®EJÁ ┬♦☼\ö"

Entiendo que es un problema al leer el JSON, pero cuando intento cargar el JSON con la libreria standard me dice:

ValueError: No JSON object could be decoded

Y si hago json.load(auth_API().read()):

AttributeError: 'str' object has no attribute 'read'

1 respuesta
elkaoD

#16 qué raro. Prueba con .decode('utf-8') pero no debería ser necesario (ni tiene pinta de ser UTF-8).

1 respuesta
Nucklear

#17 Solved, era por la compresión al pasarle la cabecera Accept-Encoding: gzip

Vaya empanada tengo hoy macho....

1

Usuarios habituales

  • Nucklear
  • elkaoD