Search code examples
google-apps-scriptgoogle-apigoogle-oauthgoogle-apps-script-api

Google Service Accounts / API - I keep getting the Error: Access not granted or expired. (line 454, file "Service")


I've searched for hours for an answer to this, so please bear with me!

I'm using Apps Script to connect to the Google Drive API. I have created a service account in Console, and collected the relevant credentials. I've got the following code:

function test()
{


function getOAuthService(user) {
var JSON = {
    "private_key": "-----BEGIN PRIVATE KEY-----\nMY KEY HERE\n",
    "client_email": "[email protected]",
    "client_id": "11111111111111",
    "user_email": "[email protected]"
};
    return OAuth2.createService("Service Account")
        .setTokenUrl('https://accounts.google.com/o/oauth2/token')
        .setPrivateKey(JSON.private_key)
        .setIssuer(JSON.client_email)
        .setSubject(JSON.user_email)
        .setPropertyStore(PropertiesService.getScriptProperties())
        .setParam('access_type', 'offline')
        .setScope('https://www.googleapis.com/auth/drive');
}

function getUserFiles() {
    var service = getOAuthService();
    service.reset();

        var url = 'https://www.googleapis.com/drive/v2/files?pageSize=1';
        var response = UrlFetchApp.fetch(url, {
            headers: {
                Authorization: 'Bearer ' + service.getAccessToken()
            }, muteHttpExceptions: true
        });
        Logger.log(response.getContentText());
    
}

function reset() {
    var service = getOAuthService();
    service.reset();
}

getUserFiles()
}

I have also added the OAuth2 library into the script.

Every time I run this I am met with the following error though: Error: Access not granted or expired. (line 454, file "Service")

Does anybody have any ideas?

Thanks!


Solution

  • How about the following modification?

    From:

    return OAuth2.createService("Service Account")
        .setTokenUrl('https://accounts.google.com/o/oauth2/token')
        .setPrivateKey(JSON.private_key)
        .setIssuer(JSON.client_email)
        .setSubject(JSON.user_email)
        .setPropertyStore(PropertiesService.getScriptProperties())
        .setParam('access_type', 'offline')
        .setScope('https://www.googleapis.com/auth/drive');
    

    To:

    return OAuth2.createService("Service Account")
        .setTokenUrl('https://accounts.google.com/o/oauth2/token')
        .setPrivateKey(JSON.private_key)
        .setIssuer(JSON.client_email)
        // .setSubject(JSON.user_email)  // <--- Removed
        .setPropertyStore(PropertiesService.getScriptProperties())
        .setParam('access_type', 'offline')
        .setScope('https://www.googleapis.com/auth/drive');
    

    Note:

    • As additional information, when the file list is retrieved by the service account, the Drive is different from your Google Drive of your Google account. Please be careful this. If you want to retrieve the file list of your Google Drive, for example, when a folder in your Google Drive is shared with the email of service account, the file list in the folder can be retrieved by the service account.

    • From Iamblichus's comments. Ref and Ref

      • The original code would have been all right if the service account had been granted domain-wide authority and the OP wanted to use the SA to impersonate another user in the domain (this is done via .setSubject, but won't work if domain-wide delegation is not set). But since the OP wanted to access the service account's Drive, and not impersonate another account, there was no reason for using .setSubject.

    Reference: