14
votes

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.

6

6 Answers

15
votes

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 = "[email protected]";

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);
    }
}
3
votes

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

3
votes

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("[email protected]")
    gmail_service = build("gmail","v1",credentials=credentials_delegated)
    addresses = gmail_service.users().settings().sendAs().list(userId='me').execute()
    assert gmail_service
2
votes

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(**[email protected]**);
        }

        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();            
   }
1
votes

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);
}