30
votes

I've some issues with Google Drive API, service account and authentication. I read a lot, but I cannot figure out how to solve this.

Context: I have some files on my Drive account (about 35GB) and a simple web app which let users to log in, see some selected folders/files from my Drive and download them if needed. The only one who can directly access to my Drive account is (or should be) my server, users do their stuff through web app/server.

After some search I found server-to-server authorization docs that should be perfect for my purpose BUT, as I can see, service account does not share same Drive space: they have their own and it's not upgradable. Because of this (weird) limit I cannot use service account since I have more than 35GB and I need to "share" everything.

Other way: use "standard" OAuth in order to obtain an access token and then use it to make call to Drive API, but access tokens have an expire date and I can't update it manually every time.

So, first question: is there a way to increase quota for service account? If not, if there a way to use my "normal" account (owner) acting like a service account?

Second (dummy) question: I read docs about creating new OAuth credentials and at the end you obtain some example code and "client-secret" JSON. I run the example but I didn't understand what the role of that JSON file is: I must log in and give permission anyway, why do I need it?

Third (dummy enough) question: if OAuth is the only solution, is there a way to obtain/refresh access tokens without doing it manually every time? I looked at OAuth docs and "user interaction/confirmation" is one of the basic thing in the auth flow, so I don't think is possible.

2
Awesome! I didn't find it before, I'll try in that way and let's see what happens... Thanks a lot!user6208310

2 Answers

87
votes

Share your Drive account's folder to your service account.
Your service account's addresss looks like [email protected].
Then your service account can see the shared folder from your Drive account.
So you have 35GB service account.

5
votes

The answer of drinkmystery is the right way to go in case your account is private and not a part of Google Workspace (formerly GSuite). Otherwise, there's a more elegant way to solve this with the createDelegated() method. It was described here and you should follow all the configuration instructions from there, but the code samples provided there are based on some deprecated packages, so it took me some time to make it work properly combining with the codes from the Drive API tutorial.

So for those who just need a working code sample, here it is (note the use of createDelegated):

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.ServiceAccountCredentials;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;

public class DelegatedDriveWithServiceAccountQuickstart {
    private static final String APPLICATION_NAME = "your awesome app";
    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();

    private static final List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE);
    private static final String CREDENTIALS_FILE_PATH = "/path/to/your/service-account-key-downloaded-from-google-api-console.json";

    public static void main(String... args) throws IOException, GeneralSecurityException {
        // Build a new authorized API client service
        final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
        HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(ServiceAccountCredentials.fromStream(new FileInputStream(CREDENTIALS_FILE_PATH))
                .createScoped(SCOPES)
                .createDelegated("[email protected]"));
        Drive driveService = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
                .setApplicationName(APPLICATION_NAME)
                .build();

        // Print the names and IDs for up to 10 files.
        FileList result = driveService.files().list()
                .setPageSize(10)
                .setFields("nextPageToken, files(id, name)")
                .execute();
        List<File> files = result.getFiles();
        if (files == null || files.isEmpty()) {
            System.out.println("No files found.");
        } else {
            System.out.println("Files:");
            for (File file : files) {
                System.out.printf("%s (%s)\n", file.getName(), file.getId());
            }
        }
    }
}

Please, make sure your project dependencies are up to date (don't use blindly those from the Drive API tutorial). Here is my build.gradle:

apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'DelegatedDriveWithServiceAccountQuickstart'
sourceCompatibility = 1.8
targetCompatibility = 1.8
version = '1.0'

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.google.api-client:google-api-client:1.31.1'
    compile 'com.google.apis:google-api-services-drive:v3-rev20201130-1.31.0'
    compile 'com.google.auth:google-auth-library-oauth2-http:0.22.2'
}