32

I am using googles official oauth2client.client to access the google plus api. I have a refresh token (that does not expire) stored in a database, and need to recreate the temporary "Credentials" (access token) from that.

But I could not find a way to do this with to official library supplied by google.

So I hacked around it: used urllib to access the API that gives me a new access_token from the refresh_token. Using the access_token I can then use the library.

I must be missing somthing!

from apiclient import discovery
from oauth2client.client import AccessTokenCredentials
from urllib import urlencode
from urllib2 import Request , urlopen, HTTPError
import json

# ==========================================

def access_token_from_refresh_token(client_id, client_secret, refresh_token):
  request = Request('https://accounts.google.com/o/oauth2/token',
    data=urlencode({
      'grant_type':    'refresh_token',
      'client_id':     client_id,
      'client_secret': client_secret,
      'refresh_token': refresh_token
    }),
    headers={
      'Content-Type': 'application/x-www-form-urlencoded',
      'Accept': 'application/json'
    }
  )
  response = json.load(urlopen(request))
  return response['access_token']

# ==========================================

access_token = access_token_from_refresh_token(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)

# now I can use the library properly
credentials = AccessTokenCredentials(access_token, "MyAgent/1.0", None)
http = credentials.authorize(httplib2.Http())
service = discovery.build('plus', 'v1', http=http)
google_request = service.people().get(userId='me')
result = google_request.execute(http=http)
bjelli
  • 9,752
  • 4
  • 35
  • 50
  • 4
    Hi, did you find a solution to refresh the token while using AccessTokenCredentials ? I am trying to find out how to do that but there is no visible documentation around ... – xavier Jan 31 '16 at 07:50
  • 2
    Just to make a dent in the history, in case somebody else is hitting his/her head to the wall: Google's documentation is complicated (or incomplete) here. `AccessTokenCredentials` doesn't provide a way to refresh the token. So you need to use other types of credentials, as explained in the answers below two possible options came feasible to me: `OAuth2Credentials` and `GoogleCredentials` (`GoogleCredentials` extends `OAuth2Credentials`). – CM. Jul 24 '17 at 23:48

11 Answers11

25

I use: oauth2client.client.GoogleCredentials

    cred = oauth2client.client.GoogleCredentials(access_token,client_id,client_secret,
                                          refresh_token,expires_at,"https://accounts.google.com/o/oauth2/token",some_user_agent)
    http = cred.authorize(httplib2.Http())
    cred.refresh(http)
    self.gmail_service = discovery.build('gmail', 'v1', credentials=cred)
leah
  • 433
  • 5
  • 7
  • 1
    I want to use the **refresh_token** (saved in a database) to retrieve a new **access_token** later. If I use `oauth2client.client.GoogleCredentials`, as you've suggested here, should I save the **access_code** also along-with the **refresh_code** in my database for future use? – user2436428 Jul 08 '17 at 15:48
  • 3
    you can set `access_token` to `None` (as you're renewing the token anyway), and `expires_at` can be `None` as well – hansaplast Dec 29 '17 at 07:52
11

You can construct an OAuth2Credentials instance directly like this:

import httplib2
from oauth2client import GOOGLE_REVOKE_URI, GOOGLE_TOKEN_URI, client

CLIENT_ID = '<client_id>'
CLIENT_SECRET = '<client_secret>'
REFRESH_TOKEN = '<refresh_token>'

credentials = client.OAuth2Credentials(
    access_token=None,  # set access_token to None since we use a refresh token
    client_id=CLIENT_ID,
    client_secret=CLIENT_SECRET,
    refresh_token=REFRESH_TOKEN,
    token_expiry=None,
    token_uri=GOOGLE_TOKEN_URI,
    user_agent=None,
    revoke_uri=GOOGLE_REVOKE_URI)

credentials.refresh(httplib2.Http())  # refresh the access token (optional)
print(credentials.to_json())
http = credentials.authorize(httplib2.Http())  # apply the credentials
Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
5

I solved this quite easily (you certainly miss this documentation). This is a snippet of my code that tries to use Picasa API to get all of album from active user:

    http = httplib2.Http(ca_certs=os.environ['REQUESTS_CA_BUNDLE'])
    try:
        http = self.oauth.credentials.authorize(http)
        response, album_list = http.request(Picasa.PHOTOS_URL, 'GET')
        if response['status'] == '403':
            self.oauth.credentials.refresh(http)
            response, album_list = http.request(Picasa.PHOTOS_URL, 'GET')
        album_list = json.load(StringIO(album_list))
    except Exception as ex:
        Logger.debug('Picasa: error %s' % ex)
        return {}

Use the refresh method coming from oauth2client.client.OAuth2Credentials. I think it's even okay to use if response['status'] != '200'. Got to check that!

swdev
  • 4,997
  • 8
  • 64
  • 106
4

In case anyone is looking for the answer for how use a refresh token with google_auth_oauthlib, the following works for me:

flow.oauth2session.refresh_token(flow.client_config['token_uri'],
                                 refresh_token=refresh_token,
                                 client_id=<MY_CLIENT_ID>,
                                 client_secret=flow.client_config['client_secret'])
creds = google_auth_oauthlib.helpers.credentials_from_session(
    flow.oauth2session, flow.client_config)

I cannot find anywhere where this is documented though.

Neil
  • 1,754
  • 2
  • 17
  • 30
4

You can also use the requests library as well:

import google.auth.transport.requests
import requests

request = google.auth.transport.requests.Request()

credentials.refresh(request)

Here is my sample code on an active project:

acct_creds = {
  'token': self.attachment.account.google_drive_access_token,
  'refresh_token': self.attachment.account.google_drive_refresh_token,
  'client_id': settings.GOOGLE_CLIENT_ID,
  'client_secret': settings.GOOGLE_CLIENT_SECRET,
  'token_uri': 'https://37947.ngrok.io/authenticate/google/callback/',
  'scopes': 'https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.install',
}
credentials = google.oauth2.credentials.Credentials(**acct_creds)
if credentials.valid:
    print("Credentials valid")
else:
    request = google.auth.transport.requests.Request()
    credentials.refresh(request)

google.auth.transport.requests module

joshlsullivan
  • 1,375
  • 2
  • 14
  • 21
3

I recommend this method.

from oauth2client import client, GOOGLE_TOKEN_URI

CLIENT_ID = "client_id"
CLIENT_SECRET = "client_secret"
REFRESH_TOKEN = "refresh_token"


credentials = client.OAuth2Credentials(
    access_token = None, 
    client_id = CLIENT_ID, 
    client_secret = CLIENT_SECRET, 
    refresh_token = REFRESH_TOKEN, 
    token_expiry = None, 
    token_uri = GOOGLE_TOKEN_URI,
    token_ id = None, 
    revoke_uri= None)

http = credentials.authorize(httplib2.Http())

Even if the access token has expired, the credential is still authorize because of the refresh token.

KiHyun Nam
  • 77
  • 6
3

If you are using the 2018 Youtube Python Quickstart demo app using google-auth, you can't use oauth2client's storage.

So here is the correct way of storing the credentials

Here is a partially working solution for google-auth, missing the correct handling of the case where the token expires:

import os
import json
import os.path
import google.oauth2.credentials
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from google_auth_oauthlib.flow import InstalledAppFlow

CLIENT_SECRETS_FILE = "client_secret.json"
SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl']
API_SERVICE_NAME = 'youtube'
API_VERSION = 'v3'

def get_authenticated_service():

  if os.path.isfile("credentials.json"):
    with open("credentials.json", 'r') as f:
      creds_data = json.load(f)
    creds = Credentials(creds_data['token'])

  else:
    flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES)
    creds = flow.run_console()
    creds_data = {
          'token': creds.token,
          'refresh_token': creds.refresh_token,
          'token_uri': creds.token_uri,
          'client_id': creds.client_id,
          'client_secret': creds.client_secret,
          'scopes': creds.scopes
      }
    print(creds_data)
    with open("credentials.json", 'w') as outfile:
      json.dump(creds_data, outfile)
  return build(API_SERVICE_NAME, API_VERSION, credentials = creds)

def channels_list(service, **kwargs):
  results = service.channels().list(**kwargs).execute()
  print('This channel\'s ID is %s. Its title is %s, and it has %s views.' %
       (results['items'][0]['id'],
        results['items'][0]['snippet']['title'],
        results['items'][0]['statistics']['viewCount']))
   
if __name__ == '__main__':
  os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
  service = get_authenticated_service()

  channels_list(service, part='snippet,contentDetails,statistics', forUsername='GoogleDevelopers')
  # or if the above doesn't work
  channels_list(service, part='snippet,contentDetails,statistics', id='YOUR_YOUTUBE_CHANNEL_ID')
Alan B
  • 4,086
  • 24
  • 33
Ray Hulha
  • 10,701
  • 5
  • 53
  • 53
  • Thanks this works! I just need it for a simple cli app – Adam Mendoza Feb 21 '19 at 23:27
  • Have amended the code because this example may not work on API v3 since channels do not necessarily have a username attached any more. See https://issuetracker.google.com/issues/35173130?pli=1#comment5 – Alan B Jul 01 '21 at 10:46
1

If you have a refresh token then you can generate credentials for use by using OAuth2Credentials as below

from googleapiclient.discovery import build
import httplib2
from oauth2client import client, GOOGLE_TOKEN_URI

client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
refresh_token = "YOUR_REFRESH_TOKEN"

creds = client.OAuth2Credentials(
            access_token = None, 
            client_id = client_id, 
            client_secret = client_secret, 
            refresh_token = refresh_token, 
            token_expiry = None, 
            token_uri = GOOGLE_TOKEN_URI,
            user_agent="pythonclient")
        
creds.refresh(httplib2.Http())

I don't know what goes in the user agent but I put a random word in there

Now you can use it to build service object and use google APIs like

service = build("drive", "v3", credentials=creds)

In case someone wants to generate and use a offline refresh token for use without having to handle the autorization since it's for your testing then use google oauth playground to generate one. Checkout this video for more information.

Monu Yadav
  • 519
  • 2
  • 17
0

You could store the entire credentials rather than only the refresh token:

json = credentials.to_json()
credentials = Credentials.new_from_json(json)

Look at the Storage object which does it this way.

tjsar
  • 35
  • 1
  • 5
  • 1
    how will that help me to gain an access token later on? the access token contained in the credentials expires, while the refresh token doesn't; so I have to get a new access token later. – bjelli Jan 12 '15 at 13:08
  • 2
    I believe credentials.authorize(http) will handle refreshing the token automatically on a 401 response. – tjsar Jan 12 '15 at 13:46
  • I got a `403` response, `insufficientPermissions Indicates that the user does not have sufficient permissions for the entity specified in the query`, which I think it needs an access token to be generated. Any idea? I am working on it currently – swdev Mar 05 '15 at 14:49
  • I use this, and it seems to be working fine for a refresh token. – James Koss Nov 20 '16 at 19:15
  • I can approve that library automatically refreshes access token when it get 401 (debugged) so answer is correct – Ivan Borshchov Aug 16 '17 at 12:50
0

Wow.. 2 years old question and not a good answer.. No surprise given that Google documentation is crap regarding this.

The correct way to do this is by extending the Storage class oauth2client.client.Storage

An example implementation(using mongodb collection _google_credentials) would be something like:

class Storage(oauth2client.client.Storage):

def __init__(self, key):
    super(Storage, self).__init__()
    self._key = key

def locked_get(self):
    if not self._key: return None
    data = _google_credentials.find_one({'_id': self._key})
    if not data: return None
    credentials = oauth2client.client.Credentials.new_from_json(json.dumps(data))
    credentials.set_store(self)
    return credentials

def locked_put(self, credentials):
    data = json.loads(credentials.to_json())
    _google_credentials.update_one({'_id': self._key}, {'$set': data}, 
        upsert=True)
    credentials.set_store(self)

def locked_delete(self):
    bucket.delete(self._key)

Then when you initially get the credentials after step2_exchange, you need to store them using Storage().put:

e.g:

credentials = flow.step2_exchange(code)
Storage(user_id).put(credentials)

When you need the credentials again, just do:

credentials = Storage(user_id).get()
599644
  • 561
  • 2
  • 15
  • The oauth2client is deprecated, https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html Google recommends you use google-auth https://google-auth.readthedocs.io/en/latest/ – Davos Dec 05 '18 at 05:42
0

If you already have a Credentials object then you can refresh it like so:

if refresh:
    import google_auth_httplib2
    # credentials instanceof google.oauth2.credentials.Credentials
    credentials.refresh(google_auth_httplib2.Request(httplib2.Http()))

I had created the Credentials object from an old token JSON file like so:

    credentials = google.oauth2.credentials.Credentials(
        token=token_json['access_token'],
        refresh_token=token_json['refresh_token'],
        id_token=token_json['id_token'],
        token_uri=token_json['token_uri'],
        client_id=token_json['client_id'],
        client_secret=token_json['client_secret'],
        scopes=token_json['scopes'])

In this way I was able to adapt some old oauth2client code.

Neil C. Obremski
  • 18,696
  • 24
  • 83
  • 112