Service Account receives HTTP code 401: Request is missing required authentication credential.

2,245 views
Skip to first unread message

Martin Levi

unread,
Nov 7, 2021, 6:36:17 PM11/7/21
to AdWords API and Google Ads API Forum
Hi,
I have a developers token for my master ad account, created a service account, created a public/private key pair, and enabled domain wide delegation.

I have successfully requested an access token (server to server in php) and have a Cron job that refreshes the token when it is around 48 minutes old.  All that part works fine.

But when I try to call the Google Ads API (again in php, server to server and without using the client libraries) using the current token, I get the HTTP 401 code.
"Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential.
UNAUTHENTICATED"

There many different elements in the overall setup and I suspect that I may have failed to "join the dots" somewhere.  The documentation is not easy to follow.

Please can someone help me to solve this.

Thanks in advance,
Martin

Google Ads API Forum Advisor

unread,
Nov 8, 2021, 4:06:24 AM11/8/21
to nitra...@gmail.com, adwor...@googlegroups.com

Hi Martin,

Thanks for reaching out to us.

Could you confirm if you already followed the instructions discussed in this document, especially the prerequisite? If yes and the issue still persists, then could you provide the following details via Reply privately to author option to further investigate?

  • User account / email address that you've used in setting up service account through API
  • Customer ID
  • Complete  request and response logs with request ID generated on your end where we can see the issue


However, kindly note that we strongly recommend using OAuth2 desktop app or web app flow instead of service accounts unless you need a domain-specific feature (for example, impersonation). OAuth2 desktop app and web app flows do require an initial user interaction for granting access to the account, but are much simpler to set up.

Regards,

Google Logo
Yasar
Google Ads API Team
 


ref:_00D1U1174p._5004Q2R74x8:ref

Martin Levi

unread,
Nov 8, 2021, 11:56:18 AM11/8/21
to AdWords API and Google Ads API Forum
Hi Yasar,

Thanks for your response.  I followed the instructions in this document which has a slightly different workflow.  I think that I have the right pieces but not set up the right way.

I will send you the information you have requested privately.

I need a service account because I will be running unattended processes, some of which are initiated by postback routines from outside my server.

Regards,
Martin

Martin Levi

unread,
Nov 23, 2021, 5:03:58 AM11/23/21
to AdWords API and Google Ads API Forum
Hi,

I sent the requested logs on November 12th, but I haven't heard back from anyone regarding the issue.  Please can someone update me?

Regards,
Martin

Google Ads API Forum Advisor

unread,
Nov 30, 2021, 3:43:58 PM11/30/21
to nitra...@gmail.com, adwor...@googlegroups.com
Hi Martin,

Apologies for the delayed response here. I'm still trying to determine the exact cause of this issue, but it's currently not quite clear. It doesn't appear related to how impersonation was set up.

It looks like you're using curl to make these requests, right? Which means you must be manually generating an access token - could you share more details on how you're generating that token? Also, have you tried using a client library with these service account credentials?

Thanks,
Ben, Google Ads API Team

ref:_00D1U1174p._5004Q2R74x8:ref

Martin Levi

unread,
Dec 1, 2021, 11:05:59 AM12/1/21
to AdWords API and Google Ads API Forum
Hi Ben,

Thanks for your reply.  Do I understand correctly that as far as you can see the setup of my Master Ad Account, the gmail account that owns it, the Master Account that owns the Developer Token, the Google Workspace and the Service Account is all OK?

Yes, I am using php curl to request and store the access token on my web server.  Here is the code, with a few redactions.  The code is executed every 6 minutes via cron, so the token is usually refreshed after 48 minutes.

<?php

// Retrieve current token from file
$tokenJsonFile  =   file_get_contents('/**********/**********.json');
$tokenData      =   json_decode($tokenJsonFile);
$expiry         =   $tokenData->expiry;
$expiry_time    =   $expiry - time();

// Refresh token if < 15 minutes to expiry
if ($expiry_time < 900) {
    // JWT Header
    $JWTHead        =   '{"alg":"RS256","typ":"JWT"}';
    $JWTHead        =   base64_encode($JWTHead);

    // JWT Claim Data
    $keyDataJsonFile = file_get_contents('/**********/**********.json');
    $keyData        =   json_decode($keyDataJsonFile);
    $iss            =   $keyData->client_email;
//    $sub            =   $keyData->client_email;
    $scope          =   "https://www.googleapis.com/auth/adwords";
    $aud            =   $keyData->token_uri;
    $iat            =   time();
    $exp            =   $iat + 3600;
//    $JWTClaim       =   '{"iss":"'.$iss.'","sub":"'.$sub.'","scope":"'.$scope.'","aud":"'.$aud.'","iat":"'.$iat.'","exp":"'.$exp.'"}';
    $JWTClaim       =   '{"iss":"'.$iss.'","scope":"'.$scope.'","aud":"'.$aud.'","iat":"'.$iat.'","exp":"'.$exp.'"}';
    $JWTClaim       =   base64_encode($JWTClaim);

    // JWT Signature
    $privateKey =   $keyData->private_key;
    openssl_sign(
                    $JWTHead.".".$JWTClaim,
                    $JWTSignature,
                    $privateKey,
                    "sha256WithRSAEncryption"
                );
    $JWTSignature = base64_encode($JWTSignature);
    
    // Post Token Request
    $curl           =   curl_init($aud);
    $parms          =   [
                        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                        'assertion' => $JWTHead.".".$JWTClaim.".".$JWTSignature
                       ];
    $data           =   http_build_query($parms);
    
    curl_setopt($curl, CURLOPT_URL, $aud);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $data);

    $response       =   curl_exec($curl);
    curl_close($curl);

    $responseData   =   json_decode($response);
    $access_token   =   $responseData->access_token;

    // If token not granted send email
    if (!isset($access_token)) {
        $tomail     =   "**********";
        $headers    =   'From: **********';
        $subject    =   "OAuth Token Not Granted: Response Data";
        mail($tomail, $subject, var_dump($responseData), $headers);
    } else {
        // otherwise write new token data to file
        $expires_in     =   $responseData->expires_in;
        $token_type     =   $responseData->token_type;
        $newExpiry      =   $iat + $expires_in;    
        $newTokenData   =   array(
                            "access_token"=>$access_token,
                            "expiry"=>$newExpiry,
                            "token_type"=>$token_type
                            );
        $newTokenJson   =   json_encode($newTokenData);
        file_put_contents(' /**********/**********.json  ', $newTokenJson);
    }
}
?>

I haven't tried using a client library - my general preference is not to have to internalize the workings of another layer of software and its attendant documentation unless there are convincing benefits to offset the time investment.

Please let me know if there is anything else I can do to assist your investigation.

Regards,
Martin

Google Ads API Forum Advisor

unread,
Dec 1, 2021, 11:58:52 AM12/1/21
to nitra...@gmail.com, adwor...@googlegroups.com
Thanks Martin,

As far as I can tell your account set up looks OK, but since it lives in Pantheon I can't examine your account directly so I can't be 100% sure.

One benefit of using a client library is it will handle the process of generating and refreshing your access token automatically, which might be very valuable in this case. While I look over your script below, would you mind running a basic test to see if the PHP client library can make a successful request with your existing service account credentials? The documentation for setting up the library is here. You should be able to execute the test locally to verify it, so that you don't have to modify your cron job script. Mainly I'm curious if you get the same error, if not, and the request succeeds, then there might be an issue with your logic that retrieves the access token.

Martin Levi

unread,
Dec 1, 2021, 1:27:10 PM12/1/21
to AdWords API and Google Ads API Forum
Hi Ben,

I'm still a little concerned about my account setup since the documentation is dispersed over several different documents that are all written for a variety of use cases.  Also the curl response contains the message: "User in the cookie is not a valid Ads user."

I will set up the client library in a few hours when my kids are in bed!  Meanwhile I will send you privately the logs from my curl request; if there is an error in my access token retrieval it should be apparent from the request log.

Regards,
Martin

Martin Levi

unread,
Dec 1, 2021, 4:43:35 PM12/1/21
to AdWords API and Google Ads API Forum
Hi Ben,

I have been diving in to the documentation on the client library from your link.  I looked at this before, and now I remember more clearly why I was reluctant to use a Client Library.

I do not have any local PHP environment.  From what I understand at first glance, to run a test locally I would need at a minimum PHP for Windows, PECL, Composer, gRPC, and only then can I install and run the PHP Client Library.  To use the Client Library on my hosted web server I obviously have PHP, and there is an option to install CodeGen_PECL (1.1.3).  It is not clear to me what other installations I would need, but in any case I don't need to touch my cron job; it is a standalone process whose only function is to provide (in a file) a continuously refreshed oauth 2.0 access token.

Is it possible to analyze the curl logs I sent you?  Here is the relevant code I ran:


$headers = [
    'User-Agent: curl',
    'Content-Type: application/json',
    'Accept: application/json',
    'Authorization: Bearer '.get_gg_oauth2_token(),
    'developer-token: '.get_gg_developer_token (),
    'login-customer-id: '.get_gg_manager_customer_id ()
];

$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $baseURL);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_VERBOSE, true);
$streamVerboseHandle = fopen(private_folder().'/curl_log.txt', 'w+');
curl_setopt($curl, CURLOPT_STDERR, $streamVerboseHandle);

$response = curl_exec($curl);
$responseFile = fopen(private_folder().'/curl_response.txt', 'w');
fwrite($responseFile, $response);
curl_close ($curl);

You can see the exact headers that were sent in the curl_log.txt file that I sent to you privately.

Regards,
Martin

Martin Levi

unread,
Dec 6, 2021, 6:30:54 AM12/6/21
to AdWords API and Google Ads API Forum
Hi Ben,

I have just read in another thread that the Master Account needs to be set up under a unique gmail address.  My Master Account is under the same email address that owns my Sub Ad Accounts.  Is this the reason for my OAuth issues?

Regards,
Martin

Google Ads API Forum Advisor

unread,
Jan 6, 2022, 1:43:55 PM1/6/22
to nitra...@gmail.com, adwor...@googlegroups.com
Hi Martin,

Apologies for the delayed response. Could you point me to that thread you're referencing? 
Reply all
Reply to author
Forward
0 new messages