0

I can run the users.list query and get the data response on the Api explorer for the Directory API (https://developers.google.com/workspace/explore?filter&discoveryUrl) and the Javascript query returns perfectly (but auth is done invisibly and probably using my superuser privileges, behind the scenes, so that does not really help me out).

I used the OAuth 2 Playground (https://developers.google.com/oauthplayground/) to create a bearer token for use with curl, and when I ran curl, it returned the data perfectly (but I don't want to use bearer tokens in production).

I took Google's sample code, and modified it to list users. When run, it looks like OAuth 2 is working. I get the 'Sign-in successful' log entry and I see a plausible 'auth' structure in the debugger.

When I step over the await service.users.list({ customer: 'my_customer' }), I catch a 400 'Execute error: GaxiosError: Invalid Input' error.

The list of scopes that I send to the google.auth.getClient in the code, is the same as on the Google Cloud Data Access page (https://console.cloud.google.com/auth/scopes), and also matches the domain-wide delegation that I gave to my 'service account' (https://admin.google.com/ac/owl/domainwidedelegation).

I saw a few old Stack Overflow notes about adding 'subject' line with my service account email address, but that made no difference and is probably an outdated clue.

Here is my code:

const { google } = require('googleapis');    // "googleapis": "^148.0.0",
const path = require('path');

  let auth;  // acquire a (OAuth 2) authentication client using a service account
  await google.auth.getClient({
    keyFile: path.join(__dirname, '../jwt.keys.json'),
    scopes: [
      'https://www.googleapis.com/auth/admin.directory.group',
      'https://www.googleapis.com/auth/admin.directory.group.member',
      'https://www.googleapis.com/auth/admin.directory.user',
      'https://www.googleapis.com/auth/admin.directory.user.readonly',
      'https://www.googleapis.com/auth/cloud-platform',
    ],
    // subject: '[email protected]',
  }).then(
    (authReturned) => {
      auth = authReturned;
      console.log('Sign-in successful');
    },
    (err) => { console.error('Error signing in', err); },
  );

  const service = google.admin({ version: 'directory_v1', auth });
  let res =
  await service.users.list({ customer: 'my_customer' }).then(
    (response) => { console.log('Response: ', response); res = response; },
    (err) => { console.error('Execute error: ', err); },
  );

Here is the error:

Execute error:  GaxiosError: Invalid Input
    at Gaxios._request (/Users/stevepodell/WebstormProjects/weconnect-server/node_modules/gaxios/build/src/gaxios.js:142:23)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async JWT.requestAsync (/Users/stevepodell/WebstormProjects/weconnect-server/node_modules/google-auth-library/build/src/auth/oauth2client.js:429:18)
    at async runSample (/Users/stevepodell/WebstormProjects/weconnect-server/controllers/groupEmailInsert.js:40:15) {
  config: {
    url: 'https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer',
    method: 'GET',
    apiVersion: '',
    userAgentDirectives: [ [Object] ],
    paramsSerializer: [Function (anonymous)],
    headers: {
      'x-goog-api-client': 'gdcl/7.0.0 gl-node/23.10.0',
      'Accept-Encoding': 'gzip',
      'User-Agent': 'google-api-nodejs-client/7.0.0 (gzip)',
      Authorization: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.'
    },
    params: { customer: 'my_customer' },
    validateStatus: [Function (anonymous)],
    retry: true,
    responseType: 'unknown',
    errorRedactor: [Function: defaultErrorRedactor],
    retryConfig: {
      currentRetryAttempt: 0,
      retry: 3,
      httpMethodsToRetry: [Array],
      noResponseRetries: 2,
      retryDelayMultiplier: 2,
      timeOfFirstRequest: 1743618542024,
      totalTimeout: 9007199254740991,
      maxRetryDelay: 9007199254740991,
      statusCodesToRetry: [Array]
    }
  },
  response: {
    config: {
      url: 'https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer',
      method: 'GET',
      apiVersion: '',
      userAgentDirectives: [Array],
      paramsSerializer: [Function (anonymous)],
      headers: [Object],
      params: [Object],
      validateStatus: [Function (anonymous)],
      retry: true,
      responseType: 'unknown',
      errorRedactor: [Function: defaultErrorRedactor]
    },
    data: { error: [Object] },
    headers: {
      'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
      'content-encoding': 'gzip',
      'content-type': 'application/json; charset=UTF-8',
      date: 'Wed, 02 Apr 2025 18:29:01 GMT',
      server: 'ESF',
      'transfer-encoding': 'chunked',
      vary: 'Origin, X-Origin, Referer',
      'x-content-type-options': 'nosniff',
      'x-frame-options': 'SAMEORIGIN',
      'x-xss-protection': '0'
    },
    status: 400,
    statusText: 'Bad Request',
    request: {
      responseURL: 'https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer'
    }
  },
  error: undefined,
  status: 400,
  code: 400,
  errors: [ { message: 'Invalid Input', domain: 'global', reason: 'invalid' } ],
  [Symbol(gaxios-gaxios-error)]: '6.7.1'
}

Debugging into Googles gaxios.io, I see the request going out:

{
  "url": "https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer",
  "method": "GET",
  "apiVersion": "",
  "userAgentDirectives": [
    {
      "product": "google-api-nodejs-client",
      "version": "7.0.0",
      "comment": "gzip"
    }
  ],
  "headers": {
    "x-goog-api-client": "gdcl/7.0.0 gl-node/23.10.0",
    "Accept-Encoding": "gzip",
    "User-Agent": "google-api-nodejs-client/7.0.0 (gzip)",
    "Authorization": "Bearer ya29.c.c0ASRK0GaAq-1fZVK(...Redacted...)2P7QKmnk7VyorYYQ"
  },
  "params": {
    "customer": "my_customer"
  },
  "retry": true,
  "responseType": "unknown"
}

And the response

{
  "config": {
    "url": "https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer",
    "method": "GET",
    "apiVersion": "",
    "userAgentDirectives": [
      {
        "product": "google-api-nodejs-client",
        "version": "7.0.0",
        "comment": "gzip"
      }
    ],
    "headers": {
      "x-goog-api-client": "gdcl/7.0.0 gl-node/23.10.0",
      "Accept-Encoding": "gzip",
      "User-Agent": "google-api-nodejs-client/7.0.0 (gzip)",
      "Authorization": "Bearer ya29.c.c0ASRK0GY2-ws0m_X7ga(...Redacted...)cJkM0hmni3e"
    },
    "params": {
      "customer": "my_customer"
    },
    "retry": true,
    "responseType": "unknown"
  },
  "data": {
    "error": {
      "code": 400,
      "message": "Invalid Input",
      "errors": [
        {
          "message": "Invalid Input",
          "domain": "global",
          "reason": "invalid"
        }
      ]
    }
  },
  "headers": {
    "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000",
    "content-encoding": "gzip",
    "content-type": "application/json; charset=UTF-8",
    "date": "Fri, 04 Apr 2025 16:39:39 GMT",
    "server": "ESF",
    "transfer-encoding": "chunked",
    "vary": "Origin, X-Origin, Referer",
    "x-content-type-options": "nosniff",
    "x-frame-options": "SAMEORIGIN",
    "x-xss-protection": "0"
  },
  "status": 400,
  "statusText": "Bad Request",
  "request": {
    "responseURL": "https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer"
  }
}

Any help, or even a link to an up-to-date documentation page, would be greatly appreciated.

2 Answers 2

0

// subject: '[email protected]',

Uncomment this line and add an email from a super administrator user account. 99% of the Google APIs require some user impersonation.

Sign up to request clarification or add additional context in comments.

Comments

0

Google has extensive (but wrong, inconsistent, outdated, etc) documentation that wasted so much of my time. Google's example code should just run, without requiring days of work. And sending a 400 "Invalid Input" is a poor way of saying that "even though you authenticated successfully with oAuth, you also need pass another hurdle before we will send the data".

qtxo had the right idea, but the syntax for the node library had either changed, or was inconsistent with the libraries for other languages. You do need to pass in the email of a super administrator user account.

Answer: For the Node library, the subject key-value pair, has to be wrapped in a clientOptions object. Full working code below.

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

async function runSample () {
  let auth;  // acquire a (OAuth 2) authentication client using a service account
  await google.auth.getClient({
    keyFile: path.join(__dirname, '../jwt.keys.json'),
    scopes: [
      'https://www.googleapis.com/auth/admin.directory.group',
      'https://www.googleapis.com/auth/admin.directory.group.member',
      'https://www.googleapis.com/auth/admin.directory.user',
      'https://www.googleapis.com/auth/admin.directory.user.readonly',
      'https://www.googleapis.com/auth/cloud-platform',
    ],
    clientOptions: {
      subject: '[email protected]',
    },
  }).then(
    (authReturned) => {
      auth = authReturned;
      console.log('Sign-in successful');
    },
    (err) => { console.error('Error signing in', err); },
  );

  const service = google.admin({ version: 'directory_v1', auth });
  let res;
  await service.users.list({ customer: 'my_customer' }).then(
    (response) => { console.log('Response: ', response); res = response; },
    (err) => { console.error('Execute error: ', err); },
  );
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.