Search code examples
google-apigoogle-drive-apigoogle-oauthgoogle-docs-apigoogle-api-explorer

Google Drive API's file Export endpoint is failing with API Key authentication?


Has anything changed recently with the Google Drive APIs and specifically the Export function, which would cause it to fail while using API Key access after 27-Mar-2018?

I have a Windows Service that creates and sends daily course emails for an educational group. The source content for each email is stored in a Google Drive, as a Google Doc, so that the faculty can update the course content easily.

This has been working flawlessly for the past year, but suddenly stopped working about 27-Mar-2018. Since then, I can retrieve the file details;

    _googleDriveHtmlContent.LoadFile(
        fileId
        );

But not the contents. When I Export the file as HTML, I immediately get a DownloadStatus.Failed from the ProgressChanged handler;

    var request = _driveService.Files.Export(
        fileId, 
        "text/html"
        );

I'm using API keys for security, rather than OAuth, since it's a UI-less service. To do this I need to mark the file folders as publicly accessible - specifically I'm using "Accessible to everyone, with link." This has been working great.

I've updated to the latest API v3 libraries through NuGet, with no change in behavior.

Using Google's API Explorer, I'm seeing a similar behavior.

I can retrieve my file successfully using the API Explorer with the get endpoint. https://developers.google.com/drive/v3/reference/files/get

  • fileId 1AIuGhzXsNuhhi0PMA1pblh0l5CCDaa1nPj8t_dasi_c
  • Authentication: API key (uses a "demo API key")

But with the export endpoint, I get an Internal Error (500)- https://developers.google.com/drive/v3/reference/files/export

  • fileId 1AIuGhzXsNuhhi0PMA1pblh0l5CCDaa1nPj8t_dasi_c
  • mimeType: text/html
  • Authentication: API key (uses a "demo API key")

Changing the Authentication in the API Explorer to OAuth 2.0, and approving access, then returns a successful 200 result with the file HTML. However I'm unable to do that since I'm accessing the API via a UI-less service.


Solution

  • Has anything changed recently with the Google Drive APIs and specifically the Export function, which would cause it to fail while using API Key access after 27-Mar-2018?

    Its possible but its most likely a stealth change that you will not get any official word on. Not that long ago i saw someone posting a similar question they were using an API key to update a Google sheet and it suddenly stopped working.

    IMO if google has changed this its probably a good thing. API keys are meant for accessing public data. Setting a document to public is a really bad idea if anyone did manage to find the file ID of your document they would then be able to update your document.

    Suggestion:

    What you should be using is a Service account. Service accounts are dummy users by creating service account credentials on Google developer console and then taking the service account email address you can share the file on Google Drive with the service account granting it access to said file without the need of making the file public.

    You havent specified what language you are using but you said you were making a windows service so i am going to assume you are using .net. Here is an example of service account authencation with the Google .net client library.

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

    code ripped from serviceaccount.cs. Assuming that you were already using the Google .net client library the service this method returns will be the same drive service you were using with an api key.

    Once you have granted your service account access to the file it will be able to access the file when ever it needs there is no authentication needed as you have preauthorized it by sharing the file with it.