28
votes

Gmail API fails for one domain when retrieving messages with this error:

com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 OK
{
  "code" : 403,
  "errors" : [ {
    "domain" : "global",
    "message" : "Delegation denied for <user email>",
    "reason" : "forbidden"
  } ],
  "message" : "Delegation denied for <user email>"
}

I am using OAuth 2.0 and Google Apps Domain-Wide delegation of authority to access the user data. The domain has granted data access rights to the application.

5
This Error has started occurring for us as well. Have had no issues to date. If we use IMAP everything is fine - looks like there is some issue with Gmail API. Google Help??PNC
Has this worked before or did it just break? If it just broke can you give the time at which it broke? If it has never worked, can you confirm it's a service account, whitelisted in Cpanel and provide more details on the domain-wide setup? You're using something like: developers.google.com/accounts/docs/… I imagine?Eric D
Can you also post what the value is you're using for the "userId" field in your requests? Is it "me", the user's email address that should match the auth token or something else?Eric D
That error should only occur if you're using a userId param that differs from the authorized user. That style of delegation isn't supported. Correct way is to impersonate the user when fetching the access token and stick to using 'me' as userId.Steve Bazyl
Just use: userId="me" when making your call to the Gmail API. For service account with domain-wide delegation the only time you specify the email address is for the 'sub' parameter when you're requesting the access token.Eric D

5 Answers

65
votes

Seems like best thing to do is to just always have userId="me" in your requests. That tells the API to just use the authenticated user's mailbox--no need to rely on email addresses.

2
votes

Our users had migrated into a domain and their account had aliases attached to it. We needed to default the SendAs address to one of the imported aliases and want a way to automate it. The Gmail API looked like the solution, but our privileged user with roles to make changes to the accounts was not working - we kept seeing the "Delegation denied for " 403 error.

Here is a PHP example of how we were able to list their SendAs settings.

<?PHP

//
// Description:
//   List the user's SendAs addresses.
//
// Documentation:
//   https://developers.google.com/gmail/api/v1/reference/users/settings/sendAs
//   https://developers.google.com/gmail/api/v1/reference/users/settings/sendAs/list
//
// Local Path:
//   /path/to/api/vendor/google/apiclient-services/src/Google/Service/Gmail.php
//   /path/to/api/vendor/google/apiclient-services/src/Google/Service/Gmail/Resource/UsersSettingsSendAs.php
//
// Version:
//    Google_Client::LIBVER  == 2.1.1
//

require_once $API_PATH . '/path/to/google-api-php-client/vendor/autoload.php';

date_default_timezone_set('America/Los_Angeles');

// this is the service account json file used to make api calls within our domain
$serviceAccount = '/path/to/service-account-with-domain-wide-delagation.json';
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $serviceAccount );

$userKey = '[email protected]';

// In the Admin Directory API, we may do things like create accounts with 
// an account having roles to make changes. With the Gmail API, we cannot 
// use those accounts to make changes. Instead, we impersonate
// the user to manage their account.

$impersonateUser = $userKey;

// these are the scope(s) used.
define('SCOPES', implode(' ', array( Google_Service_Gmail::GMAIL_SETTINGS_BASIC ) ) );

$client = new Google_Client();
$client->useApplicationDefaultCredentials();  // loads whats in that json service account file.
$client->setScopes(SCOPES); // adds the scopes
$client->setSubject($impersonateUser);  // account authorized to perform operation

$gmailObj  = new Google_Service_Gmail($client);

$res       = $gmailObj->users_settings_sendAs->listUsersSettingsSendAs($userKey);

print_r($res);


?>
2
votes

I had the same issue before, the solution is super tricky, you need to impersonate the person you need to access gmail content first, then use userId='me' to run the query. It works for me.

here is some sample code:

   users = # coming from directory service
   for user in users:
     credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)
     ####IMPORTANT######
     credentials_delegated = credentials.with_subject(user['primaryEmail'])

     gmail_service = build('gmail', 'v1', credentials=credentials_delegated)

     results = gmail_service.users().labels().list(userId='me').execute()
     labels = results.get('labels', [])
       for label in labels:
          print(label['name'])
0
votes

I wanted to access the emails of fresh email id/account but what happened was, the recently created folder with '.credentials' containing a JSON was associated with the previous email id/account which I tried earlier. The access token and other parameters present in JSON are not associated with new email id/account. So, in order make it run you just have to delete the '.credentails' folder and run the program again. Now, the program opens the browser and asks you to give permissions.

To delete the folder containing files in python

import shutil
shutil.rmtree("path of the folder to be deleted")

you may add this at the end of the program

-1
votes

Recently I started exploring Gmail API and I am following the same approach as Guo mentioned. However, it is going to take of time and too many calls when we the number of users or more. After domain wide delegation my expectation was admin id will be able to access the delegated inboxes, but seems like we need to create service for each user.