Authorization of Google Drive using JavaScript

35,075

Solution 1

It is possible to use the Google APIs Javascript client library with Drive but you have to be aware that there are some pain points.

There are 2 main issues currently, both of which have workarrounds:

Authorization

First if you have a look closely at how Google Drive auth works you will realize that, after a user has installed your Drive application and tries to open a file or create a new file with your application, Drive initiates the OAuth 2.0 authorization flow automatically and the auth parameters are set to response_type=code and access_type=offline. This basically means that right now Drive apps are forced to use the OAuth 2 server-side flow which is not going to be of any use to the Javascript client library (which only uses the client-side flow).

The issue is that: Drive initiates a server-side OAuth 2.0 flow, then the Javascript client library initiates a client-side OAuth 2.0 flow.

This can still work, all you have to do it is use server-side code to process the authorization code returned after the Drive server-side flow (you need to exchange it for an access token and a refresh token). That way, only on the first flow will the user be prompted for authorization. After the first time you exchange the authorization code, the auth page will be bypassed automatically.

Server side samples to do this is available in our documentation.

If you don't process/exchange the auth code on the server-side flow, the user will be prompted for auth every single time he tries to use your app from Drive.

Handling file content

The second issue is that uploading and accessing the actual Drive file content is not made easy by our Javascript client library. You can still do it but you will have to use custom Javascript code.

Reading the file content

When a file metadata/a file object is retrieved, it contains a downloadUrl attribute which points to the actual file content. It is now possible to download the file using a CORS request and the simplest way to auth is to use the OAuth 2 access token in a URL param. So just append &access_token=... to the downloadUrl and fetch the file using XHR or by forwarding the user to the URL.

Uploading file content

UPDATE UPDATE: The upload endpoints do now support CORS.

~~UPDATE: The upload endpoints, unlike the rest of the Drive API do not support CORS so you'll have to use the trick below for now:~~

Uploading a file is tricky because it's not built-in the Javascript client lib and you can't entirely do it with HTTP as described in this response because we don't allow cross-domain requests on these API endpoints. So you do have to take advantage of the iframe proxy used by our Javascript client library and use it to send a constructed multipart request to the Drive SDK. Thanks to @Alain, we have an sample of how to do that below:

/**
 * Insert new file.
 *
 * @param {File} fileData File object to read data from.
 * @param {Function} callback Callback function to call when the request is complete.
 */
function insertFileData(fileData, callback) {
  const boundary = '-------314159265358979323846';
  const delimiter = "\r\n--" + boundary + "\r\n";
  const close_delim = "\r\n--" + boundary + "--";

  var reader = new FileReader();
  reader.readAsBinaryString(fileData);
  reader.onload = function(e) {
    var contentType = fileData.type || 'application/octet-stream';
    var metadata = {
      'title': fileData.fileName,
      'mimeType': contentType
    };

    var base64Data = btoa(reader.result);
    var multipartRequestBody =
        delimiter +
        'Content-Type: application/json\r\n\r\n' +
        JSON.stringify(metadata) +
        delimiter +
        'Content-Type: ' + contentType + '\r\n' +
        'Content-Transfer-Encoding: base64\r\n' +
        '\r\n' +
        base64Data +
        close_delim;

    var request = gapi.client.request({
        'path': '/upload/drive/v2/files',
        'method': 'POST',
        'params': {'uploadType': 'multipart'},
        'headers': {
          'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
        },
        'body': multipartRequestBody});
    if (!callback) {
      callback = function(file) {
        console.log(file)
      };
    }
    request.execute(callback);
  }
}

To improve all this, in the future we might:

  • Let developers choose which OAuth 2.0 flow they want to use (server-side or client-side) or let the developer handle the OAuth flow entirely.
  • Allow CORS on the /upload/... endpoints
  • Allow CORS on the exportLinks for native gDocs
  • We should make it easier to upload files using our Javascript client library.

No promises at this point though :)

Solution 2

I did it. Heres my code:

<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8' />
    <style>
        p {         
            font-family: Tahoma;
        }
    </style>
  </head>
  <body>
    <!--Add a button for the user to click to initiate auth sequence -->
    <button id="authorize-button" style="visibility: hidden">Authorize</button>
    <script type="text/javascript">
      var clientId = '######';
      var apiKey = 'aaaaaaaaaaaaaaaaaaa';
      // To enter one or more authentication scopes, refer to the documentation for the API.
      var scopes = 'https://www.googleapis.com/auth/drive';

      // Use a button to handle authentication the first time.
      function handleClientLoad() {
        gapi.client.setApiKey(apiKey);
        window.setTimeout(checkAuth,1);
      }

      function checkAuth() {
        gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
      }

      function handleAuthResult(authResult) {
        var authorizeButton = document.getElementById('authorize-button');
        if (authResult && !authResult.error) {
          authorizeButton.style.visibility = 'hidden';
          makeApiCall();
        } else {
          authorizeButton.style.visibility = '';
          authorizeButton.onclick = handleAuthClick;
        }
      }

      function handleAuthClick(event) {
        gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, handleAuthResult);
        return false;
      }

      // Load the API and make an API call.  Display the results on the screen.
      function makeApiCall() {
        gapi.client.load('drive', 'v2', function() {

          var request = gapi.client.drive.files.list ( {'maxResults': 5 } );

          request.execute(function(resp) {          
            for (i=0; i<resp.items.length; i++) {
                    var titulo = resp.items[i].title;
                    var fechaUpd = resp.items[i].modifiedDate;
                    var userUpd = resp.items[i].lastModifyingUserName;

                    var fileInfo = document.createElement('li');
                    fileInfo.appendChild(document.createTextNode('TITLE: ' + titulo + ' - LAST MODIF: ' + fechaUpd + ' - BY: ' + userUpd ));                
                    document.getElementById('content').appendChild(fileInfo);
            }
          });        
        });
      }
    </script>
    <script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>    
    <p><b>These are 5 files from your GDrive :)</b></p>
    <div id="content"></div>
  </body>
</html>

You only have to change:

  • var clientId = '######';
  • var apiKey = 'aaaaaaaaaaaaaaaaaaa';

to your clientID and ApiKey from your Google API Console :)

Of course you have to create your project on Google API Console, activate the Drive API and activate Google Accounts auth in OAuth 2.0 (really eeeeasy!)

PS: it wont work locally on your PC, it will work on some hosting, and yoy must provide the url from it on the project console :)

Share:
35,075

Related videos on Youtube

Frodo Baggins
Author by

Frodo Baggins

I work on some open source projects.

Updated on July 09, 2022

Comments

  • Frodo Baggins
    Frodo Baggins almost 2 years

    I'm trying to authorize my application to integrate with Google Drive. Google documentation provides details for server based authorization and code samples for various server technologies.

    There's also a JavaScript Google API library, that has support for authorization. Down in the samples section of the wiki there is a code snippet for creating a config and calling the authorize function. I've altered the scope to be that one I believe is required for drive:

    var config = {
        'client_id': 'my_client_ID',
        'scope': 'https://www.googleapis.com/auth/drive.file'
      };
      gapi.auth.authorize(config, function() {
        console.log(gapi.auth);
      });
    

    The callback function is never called (yes, the Google API library is loaded corrected) Looking the Java Retrieve and Use OAuth 2.0 Credentials example, the client secret seems to be a parameter, should this go into the config?

    Has anyone tried this in JS, for Drive or other Google APIs? Does anyone know the best route for debugging such a problem, i.e. do I need to just step through the library and stop whinging?

    Please don't suggest doing the authorization on the server side, our application is entirely client side, I don't want any state on the server (and I understand the token refresh issues this will cause). I am familiar with the API configuration in the Google console and I believe that and the drive SDK setting are correct.

  • Nicolas Garnier
    Nicolas Garnier about 12 years
    @David I have updated my answer about using the Javascript client library as I realized there might be workarounds for most issues. Let me know if you still have questions.
  • Nicolas Garnier
    Nicolas Garnier almost 12 years
    UPDATE: We've enable CORS for the download URL (something like 2-3 weeks ago). I'm changing the response
  • Nicolas Garnier
    Nicolas Garnier almost 12 years
    UPDATE: CORS doesn't seem to work on the /upload/ endpoints. documenting this now.
  • Nicolas Garnier
    Nicolas Garnier over 11 years
    UPDATE: You can use the ?access_token=... URL parameter to authorize to the download URL, making it way easier to download or stream private files on Drive.
  • Boris Jockov
    Boris Jockov over 11 years
    Being able to choose the flow would be great. An ETA on that would be even greater!
  • Frodo Baggins
    Frodo Baggins over 11 years
    I've a multiple user issue on this, I've made a separate post - stackoverflow.com/questions/13366254/…
  • K3---rnc
    K3---rnc over 11 years
    @Nivco, could you perhaps also enable CORS for //docs.google.com/uc?export=...&id=... (the download permalink) ?
  • NycCompSci
    NycCompSci almost 11 years
    Have this been fixed since you wrote this post?
  • pinoyyid
    pinoyyid almost 11 years
    It is possible to test on a local PC. Register mylocalpc.example.com as a Javascript origin in the API console Edit your /etc/hosts (or whatever the windoze equivalent is) to add mylocalpc.example.com after the entry for 127.0.0.1 I'm running my local tests from the GAE test server, so I needed to include port 8888 as part of the Javascript origin url, ie. "mylocalpc.example.com:8888"
  • Rizwan Patel
    Rizwan Patel almost 10 years
    I activated all services still getting Access Not Configured. Please use Google Developers Console to activate the API for your project.
  • Danielo515
    Danielo515 over 9 years
    Is still not possible to manage this kind of things without server side? If my application is 100% client side, should then I include a server as a mediator in the first Oauth request?
  • Michael Chourdakis
    Michael Chourdakis over 6 years
    How can I include a token I got from server side drive auth, to the javascript library?
  • holden321
    holden321 over 6 years
    How to send a file "somename.txt" with content "File content" having authtoken without gapi.client.request ? Because in a chrome extension you can get authtoken mach more easily.
  • thdoan
    thdoan almost 6 years
    Would this solution work in a Chrome App or Extension?
  • thdoan
    thdoan almost 6 years
    "...all you have to do it is use server-side code to process the authorization code..." -- How would you do this from a Chrome App or Extension?
  • InsaurraldeAP
    InsaurraldeAP over 5 years
    I dont know now, it is a 2012 solution :(