2014-11-26 7 views
6

Sto costruendo un sito Web + backend con lo FLask Framework in cui utilizzo Flask-OAuthlib per l'autenticazione con google. Dopo l'autenticazione, il back-end deve eseguire regolarmente la scansione dell'utente su Gmail. Così attualmente gli utenti possono autenticare la mia app e io memorizzo lo access_token e lo refresh_token. Il access_token scade dopo un'ora, in modo che uno entro un'ora posso ottenere l'userinfo in questo modo:Come utilizzare un refresh_token per ottenere un nuovo access_token (usando Flask-OAuthLib)?

google = oauthManager.remote_app(
     'google', 
     consumer_key='xxxxxxxxx.apps.googleusercontent.com', 
     consumer_secret='xxxxxxxxx', 
     request_token_params={ 
      'scope': ['https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/gmail.readonly'], 
      'access_type': 'offline' 
     }, 
     base_url='https://www.googleapis.com/oauth2/v1/', 
     request_token_url=None, 
     access_token_method='POST', 
     access_token_url='https://accounts.google.com/o/oauth2/token', 
     authorize_url='https://accounts.google.com/o/oauth2/auth' 
    ) 

token = (the_stored_access_token, '') 
userinfoObj = google.get('userinfo', token=token).data 
userinfoObj['id'] # Prints out my google id 

Una volta che l'ora è finita, ho bisogno di usare il refresh_token (che c'ho memorizzato nel mio database) richiedere un nuovo access_token. Ho provato a sostituire the_stored_access_token con the_stored_refresh_token, ma questo mi dà semplicemente un errore Invalid Credentials.

In this github issue ho letto il seguente:

a prescindere da come si è ottenuto il token di accesso/token di aggiornamento (sia per mezzo di un codice di autorizzazione di sovvenzione o una risorsa password del proprietario credenziali), si scambiano allo stesso modo, da passando il token di aggiornamento come refresh_token e grant_type su 'refresh_token'.

Da questo ho capito ho dovuto creare un'applicazione remota in questo modo:

google = oauthManager.remote_app(
     'google', 
     # also the consumer_key, secret, request_token_params, etc.. 
     grant_type='refresh_token', 
     refresh_token=u'1/xK_ZIeFn9quwvk4t5VRtE2oYe5yxkRDbP9BQ99NcJT0' 
    ) 

Ma questo porta ad una TypeError: __init__() got an unexpected keyword argument 'refresh_token'. Quindi da qui sono un po 'perso.

Qualcuno sa come posso usare il refresh_token per ottenere un nuovo access_token? Tutti i suggerimenti sono ben accetti!

risposta

0

Osservando il codice sorgente per OAuthRemoteApp. Il costruttore non accetta un argomento di parole chiave chiamato refresh_token. Tuttavia prende una discussione denominata access_token_params che è an optional dictionary of parameters to forward to the access token url.

Poiché l'URL è lo stesso, ma il tipo di concessione è diverso. Mi immagino una chiamata come questo dovrebbe funzionare:

google = oauthManager.remote_app(
    'google', 
    # also the consumer_key, secret, request_token_params, etc.. 
    grant_type='refresh_token', 
    access_token_params = { 
     refresh_token=u'1/xK_ZIeFn9quwvk4t5VRtE2oYe5yxkRDbP9BQ99NcJT0' 
    } 
) 
1

Questo è come ottengo una nuova access_token per Google:

from urllib2 import Request, urlopen, URLError 
from webapp2_extras import json 
import mimetools 
BOUNDARY = mimetools.choose_boundary() 
def refresh_token() 
    url = google_config['access_token_url'] 
    headers = [ 
      ("grant_type", "refresh_token"), 
      ("client_id", <client_id>), 
      ("client_secret", <client_secret>), 
      ("refresh_token", <refresh_token>), 
      ] 

    files = [] 
    edata = EncodeMultiPart(headers, files, file_type='text/plain') 
    headers = {} 
    request = Request(url, headers=headers) 
    request.add_data(edata) 

    request.add_header('Content-Length', str(len(edata))) 
    request.add_header('Content-Type', 'multipart/form-data;boundary=%s' % BOUNDARY) 
    try: 
     response = urlopen(request).read() 
     response = json.decode(response) 
    except URLError, e: 
     ... 

funzione EncodeMultipart è preso da qui: https://developers.google.com/cloud-print/docs/pythonCode

essere sicuri di utilizzare lo stesso BOUNDARY

0

flask-oauthlib.contrib contiene un parametro denominato auto_refresh_url/refresh_token_url nel mote_app che fa esattamente ciò che volevi fare. An example come usarlo assomiglia a questo:

app= oauth.remote_app(
    [...] 
    refresh_token_url='https://www.douban.com/service/auth2/token', 
    authorization_url='https://www.douban.com/service/auth2/auth', 
    [...] 
) 

Comunque io non sono riuscito a farlo funzionare in questo modo. Tuttavia questo è possibile senza il pacchetto contrib. La mia soluzione era di catturare 401 chiamate API e reindirizzare a una pagina di aggiornamento se è disponibile un refresh_token. Il mio codice per l'aggiornamento finale appare come segue:

@app.route('/refresh/') 
def refresh(): 
    data = {} 
    data['grant_type'] = 'refresh_token' 
    data['refresh_token'] = session['refresh_token'][0] 
    data['client_id'] = CLIENT_ID 
    data['client_secret'] = CLIENT_SECRET 
    # make custom POST request to get the new token pair 
    resp = remote.post(remote.access_token_url, data=data) 

    # checks the response status and parses the new tokens 
    # if refresh failed will redirect to login 
    parse_authorized_response(resp) 

    return redirect('/') 

def parse_authorized_response(resp): 
    if resp is None: 
     return 'Access denied: reason=%s error=%s' % (
      request.args['error_reason'], 
      request.args['error_description'] 
     ) 
    if isinstance(resp, dict): 
      session['access_token'] = (resp['access_token'], '') 
      session['refresh_token'] = (resp['refresh_token'], '') 
    elif isinstance(resp, OAuthResponse): 
     print(resp.status) 
     if resp.status != 200: 
      session['access_token'] = None 
      session['refresh_token'] = None 
      return redirect(url_for('login')) 
     else: 
      session['access_token'] = (resp.data['access_token'], '') 
      session['refresh_token'] = (resp.data['refresh_token'], '') 
    else: 
     raise Exception() 
    return redirect('/') 

Spero che questo vi aiuterà. Ovviamente il codice può essere migliorato e non c'è sicuramente un modo più elegante di catturare i 401er, ma è un inizio;)

Un'altra cosa: non memorizzare i token nel cookie di Flask Session. Piuttosto usa Server Side Session da "Flask Session" che ho fatto nel mio codice!