Search code examples
javascriptgoogle-apigoogle-drive-apigoogle-api-js-client

Accessing a shared document with a logged in user results in a file not found error, but works with no authentication


I'm using https://developers.google.com/drive/v3/reference/files/get to read someone's shared (read-only) document. The document is shared globally, anyone with the link can view the contents. Having the shared url (like https://docs.google.com/document/d/acbcdef0123456789/edit?usp=sharing ), I can view the document in a browser with a logged in user and without any user (incognito mode) too. Accessing the document using javascript works as long as no user is involved. After a successful login (see below), the server responds with a "File not found" (404) error. After logout, I can access the same document again.

If I send the same request from command line using curl but without Authorization: Bearer... header, response is 200, OK. With the authorization headers, response is 404.

Working code here: http://jsbin.com/hayafubuce/edit?html,js,output

How could I access the shared document if I have a valid/logged in user???
(Sending an invite in email or sharing with a specific user is not an option.)

UPDATE: If I use the https://www.googleapis.com/auth/drive scope instead of https://www.googleapis.com/auth/drive.file everythings works fine. But I don't want my app to be able to access all file of the user, only those that it creates.

Loaded sdks:

<script type="text/javascript" src="https://apis.google.com/js/platform.js"></script>
<script type="text/javascript" src="https://apis.google.com/js/client.js"></script>

Reading the document's meta data:

var request = gapi.client.drive.files.get({
    fileId: id,
    key: APIKey,
});
request.then(function(response) {
    console.log("test sucess!!!");
}, handleErrors);

Authentication and login is like this:

gapi.load('auth2', function() {
    auth2 = gapi.auth2.init({
        client_id: '***valid-id-comes-here***.apps.googleusercontent.com',
        fetch_basic_profile: true,
        scope: 'https://www.googleapis.com/auth/drive.file'
    });
    gapi.load('client', function() {
        gapi.client.load('drive', 'v3');
    });
});

See the full working code in JSBin above.

Thank you!


Solution

  • I believe the reason the file is not accessible when the user is authenticated is because of the restrictions that drive.file places on applications.

    The definition of drive.file is that the application has "Per-file access to files created or opened by the app. File authorization is granted on a per-user basis and is revoked when the user deauthorizes the app," as described in the Drive API oAuth scope documentation.

    The application cannot open a file on behalf of a user unless the user has granted access to that specific application for that specific file. This is in addition to the user's normal restrictions on accessing the file... even if the file is normally readable by the user via the Google Drive GUI (e.g. they own the file, or the file is readable with anyone with the link), third-party applications need the additional grant of permission by the user to access the file on their behalf. Your JSBin (which is indeed beautifully crafted) demonstrates this restriction in action.

    You could verify this by pointing your JSBin application at a file in your Drive which the app has been granted permission to access. You could use GAPI's drive.files.create method to make the application create a file in the logged in user's Drive ("a file created... by the app"). Alternatively, you could integrate your application with Drive's "Open with" menu and then use "Open with" in the Google Drive GUI on a particular file ("a file ... opened by the app"). After doing either of these, the file in question should be readable in your JSBin when that user is authenticated.

    What's interesting is that your JSBin demonstrates that an application with drive.file oAuth scope can access a file anonymously, if it's viewable anonymously. That isn't expected from the description of the scope.

    As discussed in your original post and the answer from pinoyyid, it does at least allow a workaround in the case of anonymously-readable files: you can manually use fetch() or XMLHttpRequest to make the REST calls to the Google Drive REST endpoints without using Google's GAPI wrapper, and omit any credentials from the request. That would allow your application to make authenticated requests (via GAPI) and anonymous requests (through your manual REST requests) at the same time.

    This workaround wouldn't help you if the file you wish to read is not shared with "anyone with a link", but instead shared explicitly with the logged in user, though.

    Edit: I attempted to implement the workaround in my application, but there is a problem using custom code to make the request - because it does not originate from Google, it is treated as a cross-site request, and I ran into CORS issues. The solution I ended up implementing was to create a second GAPI client in an iframe, which I left unauthenticated. A sample of my code can be seen in this StackOverflow answer.