1
votes

I am using Google DRIVE API from .NET google client library and wanted to impersonate user from service account . I have read many other users facing same issue but none of fix worked for me. Below is detail.

  1. Create service account and enabled domain wide delegation (more than 3 hours now).
  2. Downloaded *.p12 file and noted down secret password
  3. Added permission drive scope with service account client id
  4. Using below code to create service and upload/get data from google drive

code

var certificate = new X509Certificate2(keyfilepath, "notasecret", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);

            string[] scopes = new string[] {
        DriveService.Scope.Drive,
        DriveService.Scope.DriveFile,
        DriveService.Scope.DriveAppdata,
        DriveService.Scope.DriveMetadata
        };

            var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceaccountemail)
            {
                Scopes = scopes,
                User = "[email protected]"


            }.FromCertificate(certificate));
            // Create the service.
            var service = new DriveService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "Drive API Sample"

                //HttpClientFactory = new ProxySupportedHttpClientFactory()

            });



var keyname = "1231312.p12";
var newservicact = "[email protected]";

            _service = this.AuthorizeServiceAccountwithMimic(newservicact,keyname);


 Google.Apis.Drive.v3.Data.File body = new Google.Apis.Drive.v3.Data.File();
 body.Name = System.IO.Path.GetFileName(_uploadFile);
body.Description = _descrp;
 body.MimeType = GetMimeType(_uploadFile);
body.Properties = customcolumns;



byte[] byteArray = System.IO.File.ReadAllBytes(_uploadFile);
                System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
                try
                {
                    FilesResource.CreateMediaUpload request = _service.Files.Create(body, stream, GetMimeType(_uploadFile));

                    request.SupportsTeamDrives = true;


                    request.ProgressChanged += Request_ProgressChanged;
                    request.ResponseReceived += Request_ResponseReceived;

                    request.Upload();

                    return request.ResponseBody;
                }

I am getting below error on first chuck of data being sent in Request_ProgressChanged event.

When exeucting google API methods, it throw below errror

Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method.", Uri:""

I have checked many forums enabled DWD and aaded application scope also.... anyone any idea please help,

1

1 Answers

0
votes

There are sevral ways to access google apis. using Oauth2 and using service accounts.

The client created and the code used for the service account is different from the client created for a browser application and the code used to authenticate with it. If you go to google developer console and create a browser client you can not use the service account code with this client. The same goes for service account code with a browser clinet id.

Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method.", Uri:""

Basically means that the client you are using is not the correct type for the code you are using.

Go back to google developer console create a service account download the service account .json file

ServiceAccount.cs

using Google.Apis.Drive.v3;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;

namespace GoogleSamplecSharpSample.Drivev3.Auth
{

    public static class ServiceAccountExample
    {

        /// <summary>
        /// Authenticating to Google using a Service account
        /// Documentation: https://developers.google.com/accounts/docs/OAuth2#serviceaccount
        /// </summary>
        /// <param name="serviceAccountEmail">From Google Developer console https://console.developers.google.com</param>
        /// <param name="serviceAccountCredentialFilePath">Location of the .p12 or Json Service account key file downloaded from Google Developer console https://console.developers.google.com</param>
        /// <returns>AnalyticsService used to make requests against the Analytics API</returns>
        public static DriveService AuthenticateServiceAccount(string serviceAccountEmail, string serviceAccountCredentialFilePath, string[] scopes)
        {
            try
            {
                if (string.IsNullOrEmpty(serviceAccountCredentialFilePath))
                    throw new Exception("Path to the service account credentials file is required.");
                if (!File.Exists(serviceAccountCredentialFilePath))
                    throw new Exception("The service account credentials file does not exist at: " + serviceAccountCredentialFilePath);
                if (string.IsNullOrEmpty(serviceAccountEmail))
                    throw new Exception("ServiceAccountEmail is required.");                

                // For Json file
                if (Path.GetExtension(serviceAccountCredentialFilePath).ToLower() == ".json")
                {
                    GoogleCredential credential;
                    using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
                    {
                        credential = GoogleCredential.FromStream(stream)
                             .CreateScoped(scopes);
                    }

                    // Create the  Analytics service.
                    return new DriveService(new BaseClientService.Initializer()
                    {
                        HttpClientInitializer = credential,
                        ApplicationName = "Drive Service account Authentication Sample",
                    });
                }
                else if (Path.GetExtension(serviceAccountCredentialFilePath).ToLower() == ".p12")
                {   // If its a P12 file

                    var certificate = new X509Certificate2(serviceAccountCredentialFilePath, "notasecret", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
                    var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
                    {
                        Scopes = scopes
                    }.FromCertificate(certificate));

                    // Create the  Drive service.
                    return new DriveService(new BaseClientService.Initializer()
                    {
                        HttpClientInitializer = credential,
                        ApplicationName = "Drive Authentication Sample",
                    });
                }
                else
                {
                    throw new Exception("Unsupported Service accounts credentials.");
                }

            }
            catch (Exception ex)
            {                
                throw new Exception("CreateServiceAccountDriveFailed", ex);
            }
        }
    }
}

If it still does not work then it means you have not properly configured domain wide delegation to the service account delegatingauthority