Feature Request: Add Option to Cache Access Token
Hi,
I'm trying to get a basic example working, but keep receiving the same error:
...
return _end_unary_response_blocking(state, call, True, None)
File "/Users/Louis/anaconda3/lib/python3.6/site-packages/grpc/_channel.py", line 466, in _end_unary_response_blocking
raise _Rendezvous(state, None, None, deadline)
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
status = StatusCode.UNAUTHENTICATED
details = "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project."
debug_error_string = "{"created":"@1537546478.122567000","description":"Error received from peer","file":"src/core/lib/surface/call.cc","file_line":1099,"grpc_message":"Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.","grpc_status":16}"
The example code is shown below:
import google.ads.google_ads.client
customer_id = "123"
client = google.ads.google_ads.client.GoogleAdsClient.load_from_storage(
"google-ads.yaml")
ga_service = client.get_service("GoogleAdsService")
query = ('SELECT campaign.id FROM campaign ORDER BY campaign.id')
results = ga_service.search(customer_id, query)
try:
for row in results:
print('Campaign with ID %d and name "%s" was found.'
% (row.campaign.id.value, row.campaign.name.value))
except google.ads.google_ads.errors.GoogleAdsException as ex:
print('Request with ID "%s" failed with status "%s" and includes the '
'following errors:' % (ex.request_id, ex.error.code().name))
My credentials file google-ads.yaml is set up as follows:
client_id: CLIENT_ID
client_secret: CLIENT_SECRET
refresh_token: REFRESH_TOKEN
developer_token: DEVELOPER_TOKEN
I assume the GoogleAdsClient generates an access_token from the refresh_token? Am I misunderstanding how to send an authenticated request?
Thanks,
Louis
Hello Louis,
Thanks for the report! I've attempted to reproduce your issue with the code you provided in both Python 2.7.13 and Python 3.5.3. Thus far, I have not been successful outside of intentionally misconfiguring my google-ads.yaml file. To that end, I was able to replicate the reported error by either specifying an incorrect developer token, or valid credentials for a Google account that lack access to the Google Ads account.
Aside from that, the only minor issue with your code snippet is that it does not include campaign.name in the query, so that wouldn't appear in the response. It otherwise appears to work intended on my machine with a fresh install on a virtualenv for the aforementioned Python versions.
client_id: CLIENT_ID client_secret: CLIENT_SECRET refresh_token: REFRESH_TOKEN developer_token: DEVELOPER_TOKEN
This looks correct, provided that CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, and DEVELOPER_TOKEN are placeholders. If the client_id, client_secret, and refresh_token values were not valid, you would receive an invalid grant error. Although, that doesn't mean that the Google account you're using to Authorize has access to the Google Ads account you're attempting to access.
I assume the GoogleAdsClient generates an access_token from the refresh_token? Am I misunderstanding how to send an authenticated request?
Yes, the refresh token will be used to produce the access token.
I'd suggest verifying the developer token, and that the Google account used to authorize in the OAuth 2.0 flow has access to the Google Ads account you're trying to access.
Regards, Mark
Hi Mark,
Thanks for the quick response on Friday, and apologies for my later response today. It turns out that I was using the incorrect refresh_token in my google-ads.yaml. Now that I've changed that, the example works as expected.
Can the client.credentials.token (access token) be cached? Or is a new access token generated when creating a new client instance?
Thanks,
Louis
Hey Louis,
Happy to hear you were able to sort that out! :-)
Can the client.credentials.token (access token) be cached? Or is a new access token generated when creating a new client instance?
It's not a feature we have implemented at this time, but we can consider it for a future update. Can you describe your use-case?
Regards, Mark
It could be useful for when performing many operations/reporting queries with many different clients (i.e. different customer ids), thus meaning it doesn't have to refresh the access token for these client instances every time if it's before the cached token expiry date 👍
This issue is on hold pending a decision to make this functionality available for all supported client libraries.
Hi all -
I'm going to mark this feature request as closed without implementing it. The rationale is that it hasn't become a priority at the team level, it hasn't been requested in a long time, and it's possible to achieve this functionality without any changes to the library by passing in an initialized google.oauth2.credentials.Credentials instance when instantiating the GoogleAdsClient class:
Retrieving an access token
# Using an already initialized GoogleAdsClient instance.
access_token = client.credentials.token
Initializing with an access token
from google.ads.googleads.client import GoogleAdsClient
from google.oauth2.credentials import Credentials
# Provide the access token from the above snippet, or another location.
credentials = Credentials(token=access_token)
# Provide the developer token from your google-ads.yaml file.
client = GoogleAdsClient(credentials=credentials, developer_token=developer_token)
# Retrieve a service and use it as normal until the above access token expires.
googleads_service = client.get_service("GoogleAdsService")