bjelli
bjelli

Reputation: 10070

Google API: getting Credentials from refresh token with oauth2client.client

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)

Upvotes: 34

Views: 50491

Answers (11)

Monu Yadav
Monu Yadav

Reputation: 570

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.

Upvotes: 1

Ray Hulha
Ray Hulha

Reputation: 11221

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')

Upvotes: 3

joshlsullivan
joshlsullivan

Reputation: 1500

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

Upvotes: 4

Neil C. Obremski
Neil C. Obremski

Reputation: 20244

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.

Upvotes: 0

Neil
Neil

Reputation: 1938

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.

Upvotes: 5

Eugene Yarmash
Eugene Yarmash

Reputation: 149716

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

Upvotes: 11

KiHyun Nam
KiHyun Nam

Reputation: 77

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.

Upvotes: 3

599644
599644

Reputation: 561

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()

Upvotes: 0

swdev
swdev

Reputation: 5147

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!

Upvotes: 5

leah
leah

Reputation: 433

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)

Upvotes: 25

tjsar
tjsar

Reputation: 35

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.

Upvotes: 0

Related Questions