2

I currently have an application that requires me to call the google drive api from the client and the server. Right now, I've already authenticated the user on the front end with auth 2.0 and I can upload files just fine.

Most of the code for this section, I've scraped together from the documentation and various blog posts.

async function uploadDocGoogle() {
    // get file
    const fileChooser = document.getElementById('config-file-upload');
    const file = fileChooser.files[0];
    
    console.log("file", file);
    
    const fileMetaData = {
        'name': file.name,
        'mimeType': file.type
    };

    var accessToken = gapi.auth.getToken().access_token; // Here gapi is used for retrieving the access token.
    await setGoogleAPIToken(accessToken);
    console.log(accessToken);

    var form = new FormData();
    form.append('metadata', new Blob([JSON.stringify(fileMetaData)], {type: 'application/json'}));
    form.append('file', file);

    var xhr = new XMLHttpRequest();
    xhr.open('post', 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id');
    xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    xhr.responseType = 'json';
    xhr.onload = () => {
        console.log(xhr.response.id); // Retrieve uploaded file ID.
        console.log(xhr);
    };
    xhr.send(form);
}

var SCOPES = 'https://www.googleapis.com/auth/drive';

var authorizeButton = document.getElementById('config-google-test');

/**
*  On load, called to load the auth2 library and API client library.
*/
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}

/**
*  Initializes the API client library and sets up sign-in state
*  listeners.
*/
function initClient() {
gapi.client.init({
  apiKey: API_KEY,
  clientId: CLIENT_ID,
  discoveryDocs: DISCOVERY_DOCS,
  scope: SCOPES
}).then(function () {
  // Listen for sign-in state changes.
  gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);

  // Handle the initial sign-in state.
  updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
  //authorizeButton.onclick = handleAuthClick;
}, function(error) {
  appendPre(JSON.stringify(error, null, 2));
});
}

/**
*  Called when the signed in status changes, to update the UI
*  appropriately. After a sign-in, the API is called.
*/
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
    console.log("Logged In");
} else {
    console.log("Logged Out");
}
}

/**
*  Sign in the user upon button click.
*/
function handleAuthClick(event) {
gapi.auth2.getAuthInstance().signIn();
}

/**
*  Sign out the user upon button click.
*/
function handleSignoutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}

Now I need to make a call to the api from the backend written in NodesJS. However, I want to authorize these calls using what I already have from the front end. The auth token that is generated in the front end for the call seems to only be temporary and so I don't think i can send that to the backend to authorize calls. I was wondering if anyone knew another way to do it? I was wondering if anyone also knew how to initialise the google api to use that token to make a call.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • @DaImTo are you syaing that if I use this refresh token to do everything, I wouldn't be able to upload from the client? I'd have to send the file to the server and upload from there? Is there no way to authorise a request in the client using a refresh token? – Victor Wang Aug 17 '20 at 16:22

2 Answers2

0

Try Below:

const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');

const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];

const TOKEN_PATH = 'token.json';// You can save token in dB as well

fs.readFile('credentials.json', (err, content) => {
  if (err) return console.log('Error loading client secret file:', err);
  // Authorize a client with credentials, then call the Google Drive API.
  authorize(JSON.parse(content), listFiles);
});

function authorize(credentials, callback) {
  const {client_secret, client_id, redirect_uris} = credentials.installed;
  const oAuth2Client = new google.auth.OAuth2(
      client_id, client_secret, redirect_uris[0]);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, (err, token) => {
    if (err) return getAccessToken(oAuth2Client, callback);
    oAuth2Client.setCredentials(JSON.parse(token));
    callback(oAuth2Client);
  });
}

function getAccessToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) => {
    rl.close();
    oAuth2Client.getToken(code, (err, token) => {
      if (err) return console.error('Error retrieving access token', err);
      oAuth2Client.setCredentials(token);
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
        if (err) return console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

function listFiles(auth) {
  const drive = google.drive({version: 'v3', auth});
  drive.files.list({
    pageSize: 10,
    fields: 'nextPageToken, files(id, name)',
  }, (err, res) => {
    if (err) return console.log('The API returned an error: ' + err);
    const files = res.data.files;
    if (files.length) {
      console.log('Files:');
      files.map((file) => {
        console.log(`${file.name} (${file.id})`);
      });
    } else {
      console.log('No files found.');
    }
  });
}

'getAccessToken' can be simplified if you want to use front-end. When you will get a account authorize to google. Google will return back you a Code. Use that code in this function. This will achieve your objective.

rl.question('Enter the code from that page here: ', (code) => {
        rl.close();
        oAuth2Client.getToken(code, (err, token) => {
          if (err) return console.error('Error retrieving access token', err);
          oAuth2Client.setCredentials(token);
          // Store the token to disk for later program executions
          fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
            if (err) return console.error(err);
            console.log('Token stored to', TOKEN_PATH);
          });
          callback(oAuth2Client);
        });
      });
Rahul Beniwal
  • 639
  • 4
  • 9
  • where do you find the code when you authorize with google? When I authorize with the code i pasted above in my original question, they don't ever display any codes – Victor Wang Aug 17 '20 at 16:33
  • They will return code when google redirect back on page after successful login. We have to set GOOGLE_REDIRECT_URI in google app. – Rahul Beniwal Aug 17 '20 at 16:36
  • Check this url https://developers.google.com/drive/api/v3/quickstart/nodejs . Authorization process is same for every google app like google drive, google calendars, google forms etc. After authorization main integration work start. I hope it helps. – Rahul Beniwal Aug 17 '20 at 16:39
0

You can use passport in NodeJS to integrate google auth 2.0, and then use that in frontend to authenticate and login users. Once a user logins you will get a token(from google auth) with other user data (name, email etc). And you can then store this in your database(or server variables/json-files).

Now, you can use session to preserve user state, or simply call the required api with the token attached(maintain its state in frontend, React Hooks? or simply cookies work too) and you can validate which user it is.

This is kind of a rough solution. But I have worked with it in a similar manner.

Mahad Ansar
  • 176
  • 3