10

I am trying to connect to google search console API using OAuth2

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

const auth = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

const searchconsole = google.webmasters({ version: 'v3', auth })

console.log(searchconsole.sites.list({}))

But I get this:

'Error: No access, refresh token, API key or refresh handler callback is set.'

I can not find a solid reference to this, I read that refresh token is deprecated, is this even possible to get the list of sites from node backend without interaction on the front end?

Álvaro
  • 2,255
  • 1
  • 22
  • 48

3 Answers3

3

I got this same error. Turns out I wasn't setting the token correctly.

I had created the Auth URL, gotten the tokens back in the response and stored them, as shown in the quickstart.

But I hit this error when I was trying to use them. I was doing this which was wrong and gave me the error:

oauth2Client.setCredentials(accessToken);

And getting the error.

After I found this link with a nice sample - https://googleapis.dev/nodejs/googleapis/latest/tasks/index.html#samples, I saw that you have to set the token as an object.

Like this:

 oauth2Client.setCredentials(
    {
      access_token: accessToken
    }
  );

That fixed the issue for me.

Joshua Dance
  • 8,847
  • 4
  • 67
  • 72
2

This is best reference site for google api : https://googleapis.dev/nodejs/googleapis/latest/tasks/index.html#samples

Try this :

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

const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// generate a url that asks permissions for webmaster ao any other google api scopes.

const scopes = [
  'https://www.googleapis.com/auth/webmaster'
];

const url = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  // If you only need one scope you can pass it as a string
  scope: scopes
});

OR

Try with token:


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/webmasters'];
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 Tasks API.
  authorize(JSON.parse(content), listConnectionNames);
});

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);
  });
}

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 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 listConnectionNames(auth) {
  const service = google.people({version: 'v1', auth});
  // ....

}

Nikola Lukic
  • 4,001
  • 6
  • 44
  • 75
  • I get the exact same error `No access, refresh token, API key or refresh handler callback is set.` – Álvaro Jan 26 '22 at 11:48
  • But can this be done in the backend with out pop ups? – Álvaro Jan 26 '22 at 12:22
  • No this is Oauth2 the consent screen must be shown to the user in a browser window. – Linda Lawton - DaImTo Jan 26 '22 at 14:17
  • 1
    @DalmTo But how do you do it then? is it back end or front end? Last time I checked fs is not available on front end – Álvaro Jan 26 '22 at 16:20
  • This is Node.js - Every time when you have running with `./node some.js` it is native terminal node.js proccess. This file credentials.json you will download from google api console for your current application. – Nikola Lukic Jan 26 '22 at 20:59
1

I think you should conslult the google drive quickstart it will show you how to set up authorization.

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/webmasters'];
// 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);
    });
  });
}

there is also webmasters query.js

'use strict';

const path = require('path');
const {google} = require('googleapis');
const {authenticate} = require('@google-cloud/local-auth');

const webmasters = google.webmasters('v3');

async function runSample() {
  // Obtain user credentials to use for the request
  const auth = await authenticate({
    keyfilePath: path.join(__dirname, '../oauth2.keys.json'),
    scopes: [
      'https://www.googleapis.com/auth/webmasters',
      'https://www.googleapis.com/auth/webmasters.readonly',
    ],
  });
  google.options({auth});

  const res = await webmasters.searchanalytics.query({
    siteUrl: 'http://jbeckwith.com',
    requestBody: {
      startDate: '2018-01-01',
      endDate: '2018-04-01',
    },
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

I would go with the first example as its shows how to store your authorization. The second one isnt going to do that.

Access without authorization

If we check the documentation for sites.list you will notice it stats

enter image description here

This method operates on private user data you will need permission of a user with access in order to access this data.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Thanks, actually the second one worked, but it popped a browser window for authentification, how can this work on the back end? – Álvaro Jan 26 '22 at 12:16
  • I guess my question is, can I use this code on node back end without the user having to authenticate? – Álvaro Jan 26 '22 at 12:24
  • No. Webmasters data is private user data, you need the permission of the user in order to access that data – Linda Lawton - DaImTo Jan 26 '22 at 12:40
  • Right, so in that case I am better off using the `gapi` example in the front end anyways? – Álvaro Jan 26 '22 at 12:45
  • it wont matter you still need to be authorized – Linda Lawton - DaImTo Jan 26 '22 at 12:47
  • If I run the first example on the node back end on a production server how can it trigger a browser window for authentification? – Álvaro Jan 26 '22 at 14:10
  • you authorize it once locally then store the refresh token in the file and the backend system will use that after the fact. – Linda Lawton - DaImTo Jan 26 '22 at 14:18
  • So you cannot put all that code in the back end only, there needs to be split between the front and back end? I am confused where does this code goes, on react or the node back end? – Álvaro Jan 26 '22 at 14:21
  • Sure you can as long as your have created a refresh token and stored it on the server you dont need the front end if this is just a cron job you can leave it there and it will work fine with the refresh token once the app is in prodcution. – Linda Lawton - DaImTo Jan 26 '22 at 14:22
  • But if this is running on the back end where is the browser window coming from for the initial authentification? – Álvaro Jan 26 '22 at 14:24
  • This code is intended for installed application single user. It is not intended as a web server application. There for you run it once on your own machine then upload your code to the server and have it running there as a cron job. – Linda Lawton - DaImTo Jan 26 '22 at 14:39
  • I am using a web application with a react front end and a node back end where it loads the sites from GSC as a list, could you please point me in the right direction to achieve that, I know I can run the GAPI on the front end, but I wonder if I could run it on the back end and keep all the IDs private? – Álvaro Jan 26 '22 at 14:45