How to use a refresh_token to get a new access_token (using Flask-OAuthLib)?
This is how I get a new access_token for google:
from urllib2 import Request, urlopen, URLErrorfrom webapp2_extras import jsonimport mimetoolsBOUNDARY = 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: ...
EncodeMultipart function is taken from here:https://developers.google.com/cloud-print/docs/pythonCode
Be sure to use the same BOUNDARY
Looking at the source code for OAuthRemoteApp. The constructor does not take a keyword argument called refresh_token. It does however take an argument called access_token_params
which is an optional dictionary of parameters to forward to the access token url
.
Since the url is the same, but the grant type is different. I imagine a call like this should work:
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' })
flask-oauthlib.contrib contains an parameter named auto_refresh_url / refresh_token_url in the remote_app which does exactely what you wanted to wanted to do. An example how to use it looks like this:
app= oauth.remote_app( [...] refresh_token_url='https://www.douban.com/service/auth2/token', authorization_url='https://www.douban.com/service/auth2/auth', [...])
However I did not manage to get it running this way. Nevertheless this is possible without the contrib package. My solution was to catch 401 API calls and redirect to a refresh page if a refresh_token is available. My code for the refresh endpoint looks as follows:
@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('/')
Hope this will help. The code can be enhanced of course and there surely is a more elegant way than catching 401ers but it's a start ;)
One other thing: Do not store the tokens in the Flask Session Cookie. Rather use Server Side Sessions from "Flask Session" which I did in my code!