google sheets api key missing when using oAuth2 - node.js

I'm trying to use the google sheets API with oAuth2 in an express application. I've followed the basic setup to read data from a sheet but when I make a request, I get back an error that I'm missing an API key in the response.
As far as I can tell I am checking to see if a token is needed using the authorize() function and if it already exists, passing it in the request. Since the token should authenticate the request, why would I be getting this error?
*I have also already allowed access to the api through my account
checking authorization token
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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 getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
making a call to grab the sheet data
async function grabSheetData () {
const authClient = await authorize(creds, ()=>{console.log('success')});
const request = {
spreadsheetId: 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
range: 'A1:C2',
valueRenderOption: 'ValueRenderOption.FORMATTED_VALUE',
dateTimeRenderOption: '[DateTimeRenderOption.SERIAL_NUMBER]',
auth: authClient, //this should be my token
}
try {
const response = (await sheets.spreadsheets.values.get(request)).data;
// TODO: Change code below to process the `response` object:
console.log(JSON.stringify(response, null, 2));
} catch (err) {
console.error(err);
}
};
grabSheetData();

You want to use Sheets API using the access token retrieved with OAuth2.
You want to achieve this using googleapis with Node.js.
You have already been able to retrieve the access token for using Sheets API.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
In your script, I think that authClient returns undefined. Because authorize has no returned values.
I think that this is the reason of your issue in your question.
Also, I think that when the correct authClient is returned, an error occurs at sheets.spreadsheets.values.get(request). Because the values of valueRenderOption: 'ValueRenderOption.FORMATTED_VALUE' and dateTimeRenderOption: '[DateTimeRenderOption.SERIAL_NUMBER]' are not correct.
When above points are reflected to your script, it becomes as follows.
Modified script:
authorize:
In this modification, I modified your script without modifying the function of getNewToken().
function authorize(credentials) {
return new Promise(resolve => { // Added
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 getNewToken(oAuth2Client, e => resolve(e)); // Modified
oAuth2Client.setCredentials(JSON.parse(token));
resolve(oAuth2Client); // Modified
});
});
}
grabSheetData:
async function grabSheetData() {
const authClient = await authorize(creds); // Modified
const request = {
spreadsheetId: 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
range: "A1:C2",
valueRenderOption: "FORMATTED_VALUE", // Modified
dateTimeRenderOption: "SERIAL_NUMBER", // Modified
auth: authClient //this should be my token
};
try {
const response = (await sheets.spreadsheets.values.get(request)).data;
// TODO: Change code below to process the `response` object:
console.log(JSON.stringify(response, null, 2));
} catch (err) {
console.error(err);
}
}
grabSheetData();
Note:
In this modification, it supposes that creds and sheets have already been declared elsewhere. So please be careful this.
References:
ValueRenderOption
DateTimeRenderOption
If I misunderstood your question and this was not the direction you want, I apologize.

Related

Unauthorized Error when Fetching Google My Business accounts under an Authenticated Google Account

I am trying to list the businesses under an authenticated Google Account. I have done the Oauth2 flow and have gotten the object containing the access_token, id_token,refresh_token and expiry_date
Now when I tried listing the businesses under that account I keep getting Unauthorized Error.
Below is my flow:
initOAuth2Client() {
return new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_REDIRECT_URL
);
}
//Authorization Code
async authenticateAgainstGoogleMyBusiness() {
let oauth2Client = module.exports.initOAuth2Client();
const scopes = [
'https://www.googleapis.com/auth/business.manage',
'https://www.googleapis.com/auth/userinfo.profile'
];
const state = ....
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
state: state,
prompt: 'consent'
});
}
and then for my Oauth Callback Handler I have this
async googleOAuthCallbackHandler(req, res) {
let oauth2Client = module.exports.initOAuth2Client();
let { code, state } = req.query;
const oauth2Client = module.exports.initOAuth2Client();
const { tokens } = await oauth2Client.getToken(code);
//this is where I saved the tokens exactly as received.
}
async fetchGoogleMyBusinessAccounts() {
let {access_token} = fetchTokenFromDatabase(); //This is how I retrieved the access tokens saved by the previous function.
console.log(`Fetching GMB Accounts`);
let accountsUrls = `https://mybusinessaccountmanagement.googleapis.com/v1/accounts`;
try {
let headers = {
'Authorization': `Bearer ${access_token}`
}
let response = axios.get(accountsUrls, {
headers: headers
});
let data = response.data;
console.log(`GMB Accounts response = ${JSON.stringify(data, null, 2)}`);
} catch (e) {
console.log('Error retrieving GMB Accounts:Full error below:');
console.log(e); //I keep getting Unauthorized even though I authorized the process on the oauth consent screen
}
}
How can I fix this?
I think you should have a look at the sample for Google drive I dont know why Google doesnt release samples for all the APIs but they dont. I put this together by altering the sample for google drive a bit. I dont have node installed right now but this should be close
Let me know if you have any issues and i can try and help you debug them.
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/business.manage'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
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);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
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);
});
});
}
/**
* Lists the names and IDs of up to 10 files.
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listFiles(auth) {
const service = google.mybusinessaccountmanagement({version: 'v1', auth});
service.accounts.get({
pageSize: 10,
fields: '*',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const accounts= res.accounts;
if (accounts.length) {
console.log('accounts:');
accounts.map((account) => {
console.log(`${account.name} (${account.id})`);
});
} else {
console.log('No files found.');
}
});
}
from comments
Lets have a look at this error message from the comments.
code: 429, errors: [ { message: "Quota exceeded for quota metric 'Requests' and limit 'Requests per minute' of service 'mybusinessaccountmanagement.googleapis.com' for consumer 'project_number:xxxxx'.", domain: 'global', reason: 'rateLimitExceeded' } ] }
When you created your project on google cloud platform you had to enable the api you were going to use.
If you go back and check the it by (tip manage button) on the left there is a note about Quota
Each api has a default quota for when you enable it. The default quota for this api is
Quota is what controls how many requests you can make to the api. Google limits us because they want us all to be able to use the api. They dont want just one app flooding the system.
As you can see the default quota for this api is 0 Which is why you are exceeding the limit already without making any requests.
At the top of this page there is a note that says
Request more quota limits or view quotas for your other services on the Quotas page, found in IAM & admin.
After a lot of digging i managed to find a link to the form for requesting additional quota Usage limits at the very bottom of the page there is a line that says
If you need higher limits, you can submit a Standard quota request.
Submit the form. I have no idea how long its going to take you to get a quota extension.

use of r1.question when using google sheets api

I am using the documentation found at https://developers.google.com/sheets/api/quickstart/nodejs in order to use the google sheets api in my node.js application. For reference, the code I'm using from the page is as below:
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
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 Sheets API.
authorize(JSON.parse(content), listMajors);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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 getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(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 while trying to retrieve 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);
});
});
}
/**
* Prints the names and majors of students in a sample spreadsheet:
* #see https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
* #param {google.auth.OAuth2} auth The authenticated Google OAuth client.
*/
function listMajors(auth) {
const sheets = google.sheets({version: 'v4', auth});
sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
console.log('Name, Major:');
// Print columns A and E, which correspond to indices 0 and 4.
rows.map((row) => {
console.log(`${row[0]}, ${row[4]}`);
});
} else {
console.log('No data found.');
}
});
}
At the line where it says rl.question('Enter the code from that page here: ', (code) => { I get the feeling that I'm supposed to input something, but I never see any obvious prompt to enter any kind of code, and thus I do not enter anything and my code goes to return console.error('Error while trying to retrieve access token', err); and I apparently never get an access token. How exactly do I enter this code, and where do I get this code from?
EDIT: Detailed flow for replicating bug.
I have the following requires at the top of my code:
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
Below is the route which I first access in the browser in order to initiate the process.
app.get("/test", function (req,res) {
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
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 Sheets API.
authorize(JSON.parse(content), listMajors);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
//const {client_secret, client_id, redirect_uris} = credentials.installed;
var client_secret = credentials.web.client_secret;
var client_id = credentials.web.client_id;
var redirect_uris = credentials.web.redirect_uris;
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 getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(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 while trying to retrieve 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);
});
});
}
/**
* Prints the names and majors of students in a sample spreadsheet:
* #see https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
* #param {google.auth.OAuth2} auth The authenticated Google OAuth client.
*/
function listMajors(auth) {
const sheets = google.sheets({version: 'v4', auth});
sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
console.log('Name, Major:');
// Print columns A and E, which correspond to indices 0 and 4.
rows.map((row) => {
console.log(`${row[0]}, ${row[4]}`);
});
} else {
console.log('No data found.');
}
});
}
})
When I access this route, the browser tries to load to something, but never gets there. In the debug console I get a prompt saying "Authorize this app by visiting this url: ..." and I go to the given url in the browser. I am already signed into google, so I am given a list of accounts to choose from where the browser says "Choose an account to continue to quickstart." I choose my google account, and then I get the "this app isn't verified screen", so I click "Advanced" towards the bottom of the screen, after which a "Go to Quickstart (unsafe)" link appears. I click this link, and then I seemingly go back to the previous oauth screen where I was given a list of accounts, except a prompt now appears asking to Grant Quickstart permission to view your google spreadsheets. I click "Allow", and then I am redirected to a page which looks similar to the previous Oath screen, except it's asking me to confirm my choices, make sure you trust quickstart etc., so I click Allow once again and I am finally redirected to the page I specified that I would go to by my redirect url. However, in my working directory I can never see token.json, and I never see the "Enter the code from that page here: " in the terminal (I am using VSCode and the terminal is set as windows powershell). When I try to enter the code I get from the url parameters (from the redirect page at the end) I get the error I mentioned before.

How to use a Google service account in the place of oAuth2Client to use Google Apps API?

I've successfully used the oAuth2Client with Node using Google's Quickstart tutorial:
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
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);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
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);
});
});
}
/**
* Lists the names and IDs of up to 10 files.
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
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.');
}
});
}
However, I want to avoid the consent screen and do a service to service authentication.
I've found Node GToken to get a token, but where do I place it in the code, and how do I use it?
const key = '-----BEGIN RSA PRIVATE KEY-----\nXXXXXXXXXXX...';
const { GoogleToken } = require('gtoken');
const gtoken = new GoogleToken({
email: 'my_service_account_email#developer.gserviceaccount.com',
scope: ['https://scope1', 'https://scope2'], // or space-delimited string of scopes
key: key
});
I've also found the following code on how to implement Service to Service Authentication, but it seems to be implemented for Google's compute service. The example states:
For example, a JWT auth client will be created when your code is running on your local developer machine, and a Compute client will be created when the same code is running on a configured instance of Google Compute Engine.
const {google} = require('googleapis');
const compute = google.compute('v1');
async function main () {
// This method looks for the GCLOUD_PROJECT and GOOGLE_APPLICATION_CREDENTIALS
// environment variables.
const auth = await google.auth.getClient({
// Scopes can be specified either as an array or as a single, space-delimited string.
scopes: ['https://www.googleapis.com/auth/compute']
});
// obtain the current project Id
const project = await google.auth.getProjectId();
// Fetch the list of GCE zones within a project.
const res = await compute.zones.list({ project, auth });
console.log(res.data);
}
main().catch(console.error);
I've found code on other sites that uses googleapis.auth.JWT, but I can't find any documentation on that for service to service accounts used with Google Apps.
Here it says that a service account isn't what's needed. Unfortunately, I don't understand what the question is to understand if the answer applies to my situation, too. What I want to do is to authorize my node.js app to use my drive without the need for me to allow it to access every time.
I don't know how to translate the Quickstart code to accommodate a service account.
I don't have access to a "Google Drive for Business" account, so I'm posting what I could do with my personal Google Drive ("My Drive").
I also read on some pages on Internet that you can't share a "Team Drive", but maybe you could add the service account as a team member.
This code lists the files in a folder on "My Drive" after I shared the folder with a service account.
// load library
const { google } = require('googleapis');
// configure a JWT auth client
const privatekey = require("./privatekey.json");
const jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
[
'https://www.googleapis.com/auth/drive.readonly',
'https://www.googleapis.com/auth/drive.metadata.readonly'
]
);
jwtClient.authorize()
.then(function (tokens) {
// console.log(tokens);
console.log("Authentication successfull.\n");
// list the files once authenticated
listFiles(jwtClient);
})
.catch(function (error) {
throw (error);
});
function listFiles(auth) {
const drive = google.drive({ version: 'v3', auth });
drive.files.list({
pageSize: 10,
fields: 'nextPageToken, files(id, name)',
// q: "name contains 'foo'"
})
.then(res => {
console.log(res.data);
})
.catch(err => console.log(err));
}
privatekey.json is the private key of the service account (downloaded and renamed) from "Google Cloud Platform", where of course "Google Drive API" is enabled.
Inside privatekey.json you can find the email address for the service account which you could then try to add it to the "Team Drive".

can't get refresh token for google api. Node js

I've got an Apps Script script that I am running remotely through Google's API. When I go to the link it gives me for the code to retrieve the access token, it says that the file requested does not exist. However, the code is in the url anyway, this gives me the access token, but not the refresh token.
I have tried adding 'access_type: offline' and 'approval_prompt: force', but those didn't change anything. Here's the code:
var { google } = require('googleapis');
var fs = require('fs');
var async = require('async');
const readline = require('readline');
// If modifying these scopes, delete token.json.
const SCOPES = [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/spreadsheets"
];
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
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 Apps Script API.
authorize(JSON.parse(content), callScriptFunction);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
// const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2("294862899955-bb0929ato2qem8cqllggrpuqpqit191v.apps.googleusercontent.com", "Ds4-q0G3QZog4UamQrc3HFrW", "https://script.google.com/oauthcallback");
// 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);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
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) console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function callScriptFunction(auth) {
var scriptId = "MwnuiFwldt-0ZLyLnoi0Q5kfoO49Cn6ao";
var script = google.script('v1');
script.scripts.run({
auth: auth,
resource: {
function: 'automateSheet',
parameters: [
process.argv[2],
process.argv[3],
process.argv[4],
process.argv[5],
process.argv[6],
process.argv[7],
process.argv[8],
]
},
scriptId: scriptId,
devMode: true
}, function (err, resp) {
if (err) {
console.log(err);
}
else {
var r = resp.data;
if ("error" in r) {
console.log("Error: %o", r.error);
} else {
console.log("Result: %o", r.response.result);
}
}
});
}
Here's the page Google gives me when I agree to allow the app access to my account:
i found the answer here:
Not receiving Google OAuth refresh token
where the guy said to go to https://myaccount.google.com/u/2/permissions?pli=1&pageId=none
then revoke your apps permission. it worked for me. because every time after the first time, the refresh token does not appear
I think there are two issues going on. One, it looks like your Authorized redirect URI isn't set correctly. It needs to be set to a page on your server that listens for the code URL parameter after the user authorizes. You can set it in the Google Cloud console under API > Credentials > 0Auth Client Settings > Authorized redirect URIs.
Second, I had an issue where sometimes I wouldn't get a refresh token. Turns out I had to add prompt: 'consent' to the generateAuthUrl request to force it to always return a refresh token, by always taking the user through the full authorization flow (instead of skipping steps if they recently authenticated). So your function would be:
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
prompt: 'consent',
});
Hope that helps. For reference: https://github.com/googleapis/google-api-nodejs-client/issues/750#issuecomment-368873635

Insufficient Permission when trying to create a folder on Google Drive via API(v3)

I am trying to create a folder on my Google Drive via the API. I am using Node, and have created a script that fetches a daily report and would like to via the API, create a folder and upload those files to it daily.
To setup, I have created a project per the doc's quick start guide. My setup looks basically the same as that boilerplate minus the function that reads the files in Drive. However, when trying to upload using the example given in the docs I get the error: Insufficient Permission.
Going through the permissions in the project's interface, there are a bunch of roles to choose from. Currently I have assigned my project as a Folder Administrator (as well as a couple other roles), which should give me permissions yet I am still confronted with this error. Perhaps I need to regenerate my client_secret.json file to reflect my updated permissions but how do I do that? I have been googling and poking around the interface for quite some time but see no way to regenerate this file.
The code I am using to create a folder looks like this, pretty close to the boilerplate given in the doc's - just to see it work:
const drive = google.drive('v3')
function createFolder (auth) {
console.log('auth:', auth)
const fileMetadata = {
'name': 'daily-report',
'mimeType': 'application/vnd.google-apps.folder',
'parents': ['1P924MEzU_1VoL6OOvWPHSo6vb1u9u0a9'],
}
drive.files.create({
auth: auth,
resource: fileMetadata,
fields: 'id'
}, function (err, file) {
if (err) {
// Handle error
console.error(err.message);
} else {
console.log('Folder Id: ', file.id);
}
});
}
Any help is appreciated.
UPDATED ERROR:
Token stored to /Users/sg/.credentials/
auth: OAuth2Client {
transporter: DefaultTransporter {},
_certificateCache: null,
_certificateExpiry: null,
_clientId: 'clientID093420402349.apps.googleusercontent.com',
_clientSecret: 'totalsecret',
_redirectUri: 'urn:ietf:wg:oauth:2.0:oob',
_opts: {},
credentials:
{ access_token: 'xxxxxxxxxxxx',
refresh_token: 'xxxxxxxxxxxx',
token_type: 'Bearer',
expiry_date: 1520651774212 } }
(node:76284) [DEP0013] DeprecationWarning: Calling an asynchronous function without callback is deprecated.
fs.js:106
throw backtrace;
^
Error: EISDIR: illegal operation on a directory, open '/Users/sg/.credentials/'
at rethrow (fs.js:101:21)
at maybeCallback (fs.js:119:42)
at Object.fs.writeFile (fs.js:1260:14)
at storeToken (/Users/sg/R2-DS/src/middleware/aptlist-report-auth.js:104:6)
at /Users/sg/R2-DS/src/middleware/aptlist-report-auth.js:85:7
at /Users/sg/R2-DS/node_modules/google-auth-library/lib/auth/oauth2client.js:95:13
at Request._callback (/Users/sg/R2-DS/node_modules/google-auth-library/lib/transporters.js:113:17)
at Request.self.callback (/Users/sg/R2-DS/node_modules/request/request.js:186:22)
at emitTwo (events.js:126:13)
at Request.emit (events.js:214:7)
I think that your script works. So can you confirm the following points again?
About the scope.
At Quickstart, the default scope is var SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];. But in order to use drive.files.create, https://www.googleapis.com/auth/drive is required to be added to the scopes.
If you modified the scope, please remove the file of drive-nodejs-quickstart.json. And run the script. By this, the authorization is run and the added scopes are reflected to the refresh token and access token.
About the version of googleapis
Recently, it is reported that there are some bugs for googleapis of v27.0.0, v26.0.1 and v25.0.0. So please confirm your version just in case. I use v24.0.0.
Whether Drive API was enabled at API console.
About this, I think that from your question, you may have already enabled it.
If these were not useful for you, I'm sorry.
Edit :
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/drive-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/drive'];
var TOKEN_DIR = './';
var TOKEN_PATH = 'drive-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the
// Drive API.
authorize(JSON.parse(content), createFolder);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function(code) {
rl.close();
oauth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
/**
* Lists the names and IDs of up to 10 files.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function createFolder(auth) {
const drive = google.drive('v3');
console.log('auth:', auth);
const fileMetadata = {
'name': 'daily-report',
'mimeType': 'application/vnd.google-apps.folder',
'parents': ['1P924MEzU_1VoL6OOvWPHSo6vb1u9u0a9'],
};
drive.files.create({
auth: auth,
resource: fileMetadata,
fields: 'id',
}, function(err, file) {
if (err) {
// Handle error
console.error(err.message);
} else {
console.log('Folder Id: ', file.id);
}
});
}

Resources