Posts tagged with oauth-2.0

I have created my service account, following the Google documentation as best I can. I created a JSON Key File and have used it successfully to create and refresh my access token, but when I try to call the Google Ads API using that access token I get a 401 with the message "User in the cookie is not a valid Ads user."

I am using a PHP cURL request, not the Google Client library.

My suspicion is that I have something set up incorrectly somewhere between the Master Ad account, the service account and the project in the Google Cloud Console, but I am finding the documentation confusing and unhelpful.

I submitted a question to the Google Ads API google group, and the support person said that my setup looked OK, but also admitted that he cannot see all of it from his end.

I have created the following pieces of the puzzle:
Google Ads Master Account
Developer Token
Project in Google Cloud Console
Service Account in Project
Private Key for Service Account
Set email of Master Ads Account to role of Owner of Service Account
Enabled Domain-Wide Delegation for the Service Account with scope "https://www.googleapis.com/auth/adwords"
Requested and received Access Token with the private key in the JSON file

Please let me know what extra details I should provide to get my issue resolved. Thanks in advance.

I have followed this guide Quick start but it did not work. I am using google-ads-api PHP library can be found here. I did follow it generated Refresh token. Client ID and Secret Id of my cloud project which which is of type WEB, Published and has access to google ads api.

My developer token is approved. after following all this I made call to google-ads-api and it said bad request after that it returned user authentication failed, request failed. After that

I followed this guide to generate access token and placed it in my ini. It still shows the bad request failed and the message 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.

Full error message is here

[2021-08-27T11:14:41.690565+02:00] google-ads.WARNING: Request made: Host: "googleads.googleapis.com", Method: "/google.ads.googleads.v8.services.KeywordPlanIdeaService/GenerateKeywordIdeas", CustomerId: 4278642742, RequestId: "Gl-RPU4lhQ9V1-u38_GVXA", IsFault: 1, FaultMessage: "["Authentication of the request failed."]" [2021-08-27T11:14:41.719424+02:00] google-ads.NOTICE: Request


Method Name: /google.ads.googleads.v8.services.KeywordPlanIdeaService/GenerateKeywordIdeas Host: googleads.googleapis.com Headers: { "x-goog-api-client": "gl-php/7.4.21 gapic/ gax/1.7.1 grpc/1.39.0", "x-goog-request-params": "customer_id=4278642742", "developer-token": "REDACTED" } Request: {"customerId":"4278642742","language":"languageConstants/1000","geoTargetConstants":["geoTargetConstants/1008021","geoTargetConstants/1014312"],"keywordPlanNetwork":"GOOGLE_SEARCH_AND_PARTNERS","keywordSeed":{"keywords":["Social media management is the process of managing your online presence on social media platforms like Facebook, Instagram, and Twitter by creating, publishing, and analyzing content you post. Managing social media also includes engaging and interacting with social media users. You can use tools, services, and social media managers to oversee your social media management.","No matter how you approach social media management, whether with the help of an agency or a toolset, it’s essential to understand more than social media management’s definition. You want to know what it includes, as well as how to make it a success for your company."]}}

Response

Headers: { "request-id": "Gl-RPU4lhQ9V1-u38_GVXA", "date": "Fri, 27 Aug 2021 09:14:47 GMT", "alt-svc": "h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"" }

Fault

Status code: 16 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. Failure: {"errors":[{"errorCode":{"authenticationError":"AUTHENTICATION_ERROR"},"message":"Authentication of the request failed."}],"requestId":"Gl-RPU4lhQ9V1-u38_GVXA"} Request with ID 'Gl-RPU4lhQ9V1-u38_GVXA' has failed. Google Ads failure details: authentication_error: Authentication of the request failed.

The code I am running

     public static function main(){                 if (!class_exists(Server::class)) {                     echo 'Please install "react/http" package to be able to run this example';                     exit(1);                 }                          $loop = Factory::create();                 // Creates a socket for localhost with random port.                 $socket = new \React\Socket\Server(0, $loop);                          print 'Enter your OAuth2 client ID here: ';                 $clientId = trim(fgets(STDIN));                          print 'Enter your OAuth2 client secret here: ';                 $clientSecret = trim(fgets(STDIN));                          $redirectUrl = str_replace('tcp:', 'http:', $socket->getAddress());                 $oauth2 = new OAuth2(                     [                         'clientId' => $clientId,                         'clientSecret' => $clientSecret,                         'authorizationUri' => self::AUTHORIZATION_URI,                         'redirectUri' => $redirectUrl . self::OAUTH2_CALLBACK_PATH,                         'tokenCredentialUri' => CredentialsLoader::TOKEN_CREDENTIAL_URI,                         'scope' => self::SCOPE,                         // Create a 'state' token to prevent request forgery. See                         // https://developers.google.com/identity/protocols/OpenIDConnect#createxsrftoken                         // for details.                         'state' => sha1(openssl_random_pseudo_bytes(1024))                     ]                 );                          $authToken = null;                          $server = new Server(                     $loop,                     function (ServerRequestInterface $request) use ($oauth2, $loop, &$authToken){                         // Stops the server after tokens are retrieved.                         if (!is_null($authToken)) {                             $loop->stop();                         }                                  // Check if the requested path is the one set as the redirect URI.                         if (                             $request->getUri()->getPath()                             !== parse_url($oauth2->getRedirectUri(), PHP_URL_PATH)                         ) {                             return new Response(                                 404,                                 ['Content-Type' => 'text/plain'],                                 'Page not found'                             );                         }                                  // Exit if the state is invalid to prevent request forgery.                         $state = $request->getQueryParams()['state'];                         if (empty($state) || ($state !== $oauth2->getState())) {                             throw new UnexpectedValueException(                                 "The state is empty or doesn't match expected one." . PHP_EOL                             );                         };                                  // Set the authorization code and fetch refresh and access tokens.                         $code = $request->getQueryParams()['code'];                         $oauth2->setCode($code);                         $authToken = $oauth2->fetchAuthToken();                                  $refreshToken = $authToken['refresh_token'];                         print 'Your refresh token is: ' . $refreshToken . PHP_EOL;                                  $propertiesToCopy = '[GOOGLE_ADS]' . PHP_EOL;                         $propertiesToCopy .= 'developerToken = "INSERT_DEVELOPER_TOKEN_HERE"' . PHP_EOL;                         $propertiesToCopy .=  <<<EOD         ; Required for manager accounts only: Specify the login customer ID used to authenticate API calls.         ; This will be the customer ID of the authenticated manager account. You can also specify this later         ; in code if your application uses multiple manager account + OAuth pairs.         ; loginCustomerId = "INSERT_LOGIN_CUSTOMER_ID_HERE"         EOD;                         $propertiesToCopy .= PHP_EOL . '[OAUTH2]' . PHP_EOL;                         $propertiesToCopy .= "clientId = \"{$oauth2->getClientId()}\"" . PHP_EOL;                         $propertiesToCopy .= "clientSecret = \"{$oauth2->getClientSecret()}\"" . PHP_EOL;                         $propertiesToCopy .= "refreshToken = \"$refreshToken\"" . PHP_EOL;                                  print 'Copy the text below into a file named "google_ads_php.ini" in your home '                             . 'directory, and replace "INSERT_DEVELOPER_TOKEN_HERE" with your developer '                             . 'token:' . PHP_EOL;                         print PHP_EOL . $propertiesToCopy;                                  return new Response(                             200,                             ['Content-Type' => 'text/plain'],                             'Your refresh token has been fetched. Check the console output for '                                 . 'further instructions.'                         );                     }                 );                          $server->listen($socket);                 printf(                     'Log into the Google account you use for Google Ads and visit the following URL '                         . 'in your web browser: %1$s%2$s%1$s%1$s',                     PHP_EOL,                     $oauth2->buildFullAuthorizationUri(['access_type' => 'offline'])                 );                          $loop->run();             }          ```                   Lastly this is my                   google_ads_php.ini file                   ```[GOOGLE_ADS]         ; Required AdWords API properties. Details can be found at:         ; https://developers.google.com/adwords/api/docs/guides/basic-concepts#soap_and_xml         developerToken = "AIzaSyDpRMH__DIsA6mdCchZKJeFoDwq4l*****"         clientCustomerId = "427-864-*****"                  ; Optional. Set a friendly application name identifier.         ; userAgent = "INSERT_USER_AGENT_HERE"                  ; Optional additional AdWords API settings.         ; endpoint = "https://adwords.google.com/"         ; isPartialFailure = false                  ; Optional setting for utility usage tracking in the user agent in requests.         ; Defaults to true.         ; includeUtilitiesInUserAgent = true                  [ADWORDS_REPORTING]         ; Optional reporting settings.         ; isSkipReportHeader = false         ; isSkipColumnHeader = false         ; isSkipReportSummary = false         ; isUseRawEnumValues = false                  [OAUTH2]         ; Required OAuth2 credentials. Uncomment and fill in the values for the         ; appropriate flow based on your use case. See the README for guidance:         ; https://github.com/googleads/googleads-php-lib/blob/master/README.md#getting-started                  ; For installed application or web application flow.         [OAUTH2]         clientId = "4016385****-eu0426e2lm7tlin2qa0kou4qfk80****.apps.googleusercontent.com"         clientSecret = "DPBf9pMtBHWZSk2hB*******"         refreshToken = "1//03nOACQc-vMyhCgYIARAAGAMSNwF-L9Irj8qSRZlvPdzR4n6_EbfHMLtt_tZNxJpOmZFJPLG7EAuaI-hYPB1GDQ-************"         access_token= "ya29.a0ARrdaM-eqkzlCJLcqOmVvVujzjaYnuzi3cfUGKrEG3GTlGpaoJ5Z3feK5eL_l7VrwnYYPFRqjqlDQ1eQO9FA2M8VsKbwXAI77NUsz7QxHCTo65YJHSQ5QzDOMu8Xkrzcp2Kg8KYKotTHVJ2Kiw**********"         ; For service account flow.         ; jsonKeyFilePath = "INSERT_ABSOLUTE_PATH_TO_OAUTH2_JSON_KEY_FILE_HERE"         ; scopes = "https://www.googleapis.com/auth/adwords"         ; impersonatedEmail = "INSERT_EMAIL_OF_ACCOUNT_TO_IMPERSONATE_HERE"                  [SOAP]         ; Optional SOAP settings. See SoapSettingsBuilder.php for more information.         ; compressionLevel = <COMPRESSION_LEVEL>                  [CONNECTION]         ; Optional proxy settings to be used by requests.         ; If you don't have username and password, just specify host and port.         ; proxy = "protocol://user:pass@host:port"         ; Enable transparent HTTP gzip compression for all reporting requests.         ; enableReportingGzip = false                  [LOGGING]         ; Optional logging settings.         ; soapLogFilePath = "path/to/your/soap.log"         ; soapLogLevel = "INFO"         ; reportDownloaderLogFilePath = "path/to/your/report-downloader.log"         ; reportDownloaderLogLevel = "INFO"         ; batchJobsUtilLogFilePath = "path/to/your/bjutil.log"         ; batchJobsUtilLogLevel = "INFO"

I made a script using Google Ads API to get reports from Google Ads. But the refresh token expires every 7 days. I found out that it happens because the app should be published (have publishing status "In production" instead of "Testing"). So I did it. But then the Verification status changed to "Needs verification". An option "Prepare for verification" appears

If I try to "Prepare for verification" than it requires to authorize domain of my app. So this is an option for cases when someone wants to make an app for multiple users.

But it's not my case, I just get reports from my own Google Ads account. I found out that probably the problem could be solved if I change User type of my app from "External" to "Internal". But it seems that it only an option for Google Workspace users.

But as I see on their site it is not free.

I am sure there should be some free option how to do this. Everything else works fine, the problem is just the refresh token expiration. Maybe I missed something, please help.

It seems the Google Ads API Client Library (PHP in my case) can automatically handle the access tokens by using a provided refresh token.

Does this mean that the client library will end up making additional calls in order to generate a new access token on every request?

If so, would it be better if I store the access token and pass it with each request and then track when it expires and handle generating a new one myself?

i am using java library client for web application authentication, i produce authorization url using client secret and client id,also i provided a redirect url within google api console,but i don't know if it is necessary for me to create this server to receive refresh token? i mean in production i should provide a separate server to receive the refresh token?(redirect url comes to this server) the main problem is user should paste the produced url on browser by himself but i want to open browser authmaticly , the second one is about reciving the refresh token i am not sure about creating another server to recieve refreshcode and i can't use service accounts i am going with web flow authentication.

 UserAuthorizer userAuthorizer =                 UserAuthorizer.newBuilder()                         .setClientId(ClientId.of(clientId, clientSecret))                         .setScopes(SCOPES)                         .setCallbackUri(URI.create(OAUTH2_CALLBACK_URL_CONFIGURED_AT_GOOGLE_CONSOLE))                         .build();         baseUri = URI.create("http://localhost:" + simpleCallbackServer.getLocalPort());         System.out.printf(                 "Paste this url in your browser:%n%s%n",                 userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri)); 

and this is local server to receive refresh token:

private static class SimpleCallbackServer extends ServerSocket {         private AuthorizationResponse authorizationResponse;         SimpleCallbackServer() throws IOException {             // Passes a port # of zero so that a port will be automatically allocated.             super(0);         }         /**          * Blocks until a connection is made to this server. After this method completes, the          * authorizationResponse of this server will be set, provided the request line is in the          * expected format.          */         @Override         public Socket accept() throws IOException {             Socket socket = super.accept();         } }