23

I have a desktop application to read mail using GMAIL API over REST Interface. I want to use service account so that we can download the mails using domain setting and user interaction is null. I am successfully able to create Gmail Service instance but when I try to access any Gmail API method like fetching mail list or any other I get an exception saying

Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"access_denied", Description:"Requested client not authorized."

I am done with all the setting at developer console and added scopes to my gapps domain.

Does Gmail API support service account? Using the same setting and service account I am able to get list of all files in Google drive using Drive service and API.

johnnyRose
  • 7,310
  • 17
  • 40
  • 61
Haseena Parkar
  • 939
  • 2
  • 12
  • 28

6 Answers6

17

I use the following C# code for accessing Gmail from Service Account

String serviceAccountEmail =
    "999999999-9nqenknknknpmdvif7onn2kvusnqct2c@developer.gserviceaccount.com";

var certificate = new X509Certificate2(
    AppDomain.CurrentDomain.BaseDirectory +
        "certs//fe433c710f4980a8cc3dda83e54cf7c3bb242a46-privatekey.p12",
    "notasecret",
    X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);

string userEmail = "user@domainhere.com.au";

ServiceAccountCredential credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(serviceAccountEmail)
    {
        User = userEmail,
        Scopes = new[] { "https://mail.google.com/" }
    }.FromCertificate(certificate)
);

if (credential.RequestAccessTokenAsync(CancellationToken.None).Result)
{   
    GmailService gs = new GmailService(
        new Google.Apis.Services.BaseClientService.Initializer()
        {
            ApplicationName = "iLink",
            HttpClientInitializer = credential
        }
    );

    UsersResource.MessagesResource.GetRequest gr =
        gs.Users.Messages.Get(userEmail, msgId);
    gr.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Raw;
    Message m = gr.Execute();

    if (gr.Format == UsersResource.MessagesResource.GetRequest.FormatEnum.Raw)
    {
        byte[] decodedByte = FromBase64ForUrlString(m.Raw);
        string base64Encoded = Convert.ToString(decodedByte);
        MailMessage msg = new MailMessage();
        msg.LoadMessage(decodedByte);
    }
}
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
PNC
  • 1,932
  • 19
  • 36
  • even I am following the same process but getting the exception, could you please tell me what all scope list i need to add at my google apps domain level? – Haseena Parkar Jul 17 '14 at 05:25
  • I am using a marketplace app. I have https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://mail.google.com/ – PNC Jul 17 '14 at 05:28
  • Also, if you are using the constant GmailService.Scope.MailGoogleCom (instead of writing the url), then you must append the "/" to make it work! – Néstor Sánchez A. Mar 25 '15 at 03:47
  • 4
    The userEmail, is that a regular gmail account or a domain account? Becasue for this code I am getting this unauthorized_client\", Description:\"Unauthorized client or scope in request error. Even tho I have the APIs enabled from the developer console. – Rahatur Feb 17 '16 at 01:50
  • Hi @Rahat, this is a bit late but yes it is a regular user email account from within the domain. – PNC Jul 18 '16 at 21:44
  • @PNC, thanks for the reply. Never too late. So its a domain account not a stand alone gmail account like xyz@gmail.com. I was wondering if the API works for regular gmail account not domain accounts. – Rahatur Jul 19 '16 at 20:08
  • Hi @Rahat - no its a regular user account paul@xyz.com. Because we are using a service account (certificate and secret to authenticate) we have access to all users in the domain. You just stipulate the users email and then we have access via API to their data. – PNC Jul 19 '16 at 21:50
  • 1
    @PNC, if the email addresses are like paul@xyz.com then isn't it a domain email account? – Rahatur Jul 19 '16 at 21:56
  • 1
    Hi @Rahat - I get what you are asking now... Yes you are right the service account is for a domain Gmail instance. not paul@gmail.com. – PNC Jul 20 '16 at 01:38
6

Here is a little bit of python 3.7:

from google.oauth2 import service_account
from googleapiclient.discovery import build

def setup_credentials():
    key_path = 'gmailsignatureproject-zzz.json'
    API_scopes =['https://www.googleapis.com/auth/gmail.settings.basic',
                 'https://www.googleapis.com/auth/gmail.settings.sharing']
    credentials = service_account.Credentials.from_service_account_file(key_path,scopes=API_scopes)
    return credentials


def test_setup_credentials():
    credentials = setup_credentials()
    assert credentials


def test_fetch_user_info():
    credentials = setup_credentials()
    credentials_delegated = credentials.with_subject("tim@vci.com.au")
    gmail_service = build("gmail","v1",credentials=credentials_delegated)
    addresses = gmail_service.users().settings().sendAs().list(userId='me').execute()
    assert gmail_service
Tim Richardson
  • 6,608
  • 6
  • 44
  • 71
  • It is not recommended to share code only. Please provide context to what your code does and how it answers the OP's question. – David Zwart Feb 08 '22 at 16:11
3

If you want to "read mail" you'll need the newer Gmail API (not the older admin settings API that 'lost in binary' pointed out). Yes you can do this with oauth2 and the newer Gmail API, you need to whitelist the developer in Cpanel and create a key you can sign your requests with--it take a little bit to setup: https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset

Eric D
  • 6,901
  • 1
  • 15
  • 26
  • Interesting! Am I understanding correctly that using this method one should be able to use a service account to access the new Gmail API methods across an entire Google Apps domain's users? Does the response contain information on specific user that the returned results are for? Accessing Gmail API using traditional Oauth, a user context is implied so the response doesn't need to contain contextual user info such as user Id, etc. – Austin Wang Aug 26 '14 at 18:10
  • 1
    More an oauth2 question at this point, so hopefully one of the experts on auth can comment however my understanding is as part of each request you'll generate the userId (email address) you want to access with that request. You'll use your private key, etc to form this. So you will only be authorized once for the entire domain (in Cpanel) but you can request access for any user in the domain and you'll obviously know who it is for beforehand. – Eric D Aug 26 '14 at 18:15
  • 1
    Does it work for regular gmail account? Or it has to be a domain account? – Rahatur Feb 17 '16 at 01:49
3

For C# Gmail API v1, you can use the following code to get the gmail service. Use gmail service to read emails. Once you create the service account in Google Console site, download the key file in json format. Assuming the file name is "service.json".

    public static GoogleCredential GetCredenetial(string serviceAccountCredentialJsonFilePath)
    {
        GoogleCredential credential;

        using (var stream = new FileStream(serviceAccountCredentialJsonFilePath, FileMode.Open, FileAccess.Read))
        {
            credential = GoogleCredential.FromStream(stream)
                .CreateScoped(new[] {GmailService.Scope.GmailReadonly})
                .CreateWithUser(**impersonateEmail@email.com**);
        }

        return credential;
    }

    public static GmailService GetGmailService(GoogleCredential credential)
    {
        return new GmailService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credential,                
            ApplicationName = "Automation App",                
        });
    }

   // how to use
   public static void main()
   {
        var credential = GetCredenetial("service.json");
        var gmailService = GetGmailService(credential);

        // you can use gmail service to retrieve emails. 
        var mMailListRequest = gmailService.Users.Messages.List("me");
        mMailListRequest.LabelIds = "INBOX";

        var mailListResponse = mMailListRequest.Execute();            
   }
Circuit Breaker
  • 3,298
  • 4
  • 17
  • 19
  • I get the error `Google.Apis.Auth.OAuth2.Responses.TokenResponseException: 'Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.", Uri:""'` How can I add permissions to the service account? – Tono Nam Sep 19 '19 at 20:59
  • 1
    You need to assign scopes. See how to [Delegate domain-wide authority to your service account](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account). For step 6, you can assign the scope ``https://www.googleapis.com/auth/gmail.readonly`` if you want to grant read only access. You can get a list of scopes from [here](https://developers.google.com/identity/protocols/googlescopes#gmailv1) – Circuit Breaker Sep 20 '19 at 15:20
  • Also the scopes have to match. Watever scopes I add at https://admin.google.com/ublux.com/AdminHome?chromeless=1#OGX:ManageOauthClients have to match in the code! Thanks so much for the help Circuit Breaker I finally made this work! – Tono Nam Sep 20 '19 at 23:48
2

Yes you can... check the delegation settings...

https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account

Edit: Use the link Eric DeFriez shared.

lost in binary
  • 544
  • 1
  • 4
  • 11
  • I am still getting the above exception. Following are my scope list:: User Provisioning (Read only) https://apps-apis.google.com/a/feeds/user/#readonly https://docs.google.com/feeds Email (Read/Write/Send) https://mail.google.com/ https://www.googleapis.com/auth/admin.directory.group https://www.googleapis.com/auth/admin.directory.user https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/gmail.compose https://www.googleapis.com/auth/gmail.modify https://www.googleapis.com/auth/gmail.readonly Do I need to add more scope list? – Haseena Parkar Jul 17 '14 at 05:02
  • i am using a trial account and not getting this option, just says 'Manage OAuth Client access'. is it because of the trial or is there some configuration required – heyyan khan Aug 06 '14 at 11:37
2

You can access any user@YOUR_DOMAIN.COM mails/labels/threads etc. with the new Gmail API:

https://developers.google.com/gmail/api/

via service account with impersonation (service account is accessing api as if it was specific user from your domain).

See details here: https://developers.google.com/identity/protocols/OAuth2ServiceAccount

Here is relevant code in Dartlang:

import 'package:googleapis_auth/auth_io.dart' as auth;
import 'package:googleapis/gmail/v1.dart' as gmail;
import 'package:http/http.dart' as http;

 ///credentials created with service_account here  https://console.developers.google.com/apis/credentials/?project=YOUR_PROJECT_ID 
final String creds = r'''
{
  "private_key_id": "FILL_private_key_id",
  "private_key": "FILL_private_key",
  "client_email": "FILL_service_account_email",
  "client_id": "FILL_client_id",
  "type": "service_account"
}''';


Future<http.Client> createImpersonatedClient(String impersonatedUserEmail, List scopes) async {
  var impersonatedCredentials = new auth.ServiceAccountCredentials.fromJson(creds,impersonatedUser: impersonatedUserEmail);
  return auth.clientViaServiceAccount(impersonatedCredentials  , scopes);
}



getUserEmails(String userEmail) async { //userEmail from YOUR_DOMAIN.COM
  var client = await  createImpersonatedClient(userEmail, [gmail.GmailApi.MailGoogleComScope]);
  var gmailApi = new gmail.GmailApi(client);
  return gmailApi.users.messages.list(userEmail, maxResults: 5);
}
tomaszkubacki
  • 3,181
  • 3
  • 23
  • 36