googleapis / google-api-nodejs-client

Google's officially supported Node.js client library for accessing Google APIs. Support for authorization and authentication with OAuth 2.0, API Keys and JWT (Service Tokens) is included.
https://googleapis.dev/nodejs/googleapis/latest/
Apache License 2.0
11.38k stars 1.92k forks source link

Gmail API with service account- Precondition check failed #2322

Closed vettloffah closed 3 years ago

vettloffah commented 4 years ago

I'm trying to connect with the google API's for the first time, and when I attempt to make a request to the gmail API I'm getting a Precondition check failed error. I am using a service account authorization, not Oauth2 user consent. Things I've tried:

  1. Authorized "domain wide delegation" for the service account.
  2. Ensured the APP is trusted in the G suite account.
  3. Have also tried just returning the projectId from the API - and that works. So it is authorizing and connecting. It's when I try to make a request to gmail specifically that's giving me issue.
  4. Service account role is "owner"

This is adapted from a sample, but the sample did not use service account auth so I wasn't able to use the sample directly.

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

const gmail = google.gmail('v1');

async function runSample() {
  // Obtain user credentials to use for the request
  const auth = new google.auth.GoogleAuth({
    keyFile: path.resolve(__dirname, 'google-key.json'),
    scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
  });
  google.options({auth});

  const res = await gmail.users.messages.list({userId: 'me'}); // have tried with my gsuite email address as well
  console.log(res.data);
  return res.data;
}

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

returning Error: Precondition check failed.

vettloffah commented 4 years ago

I figured out how to solve the issue.

I would like to sbumit a pull request for this issue to add a sample to the gmail sample library. It would look something like this (can work on refactoring to match code style in other samples if you give me the go-ahead on submitting a PR):

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

async getMessageList(userId, qty) {

  const JWT = google.auth.JWT;
  const authClient = new JWT({
    keyFile: path.resolve(__dirname, 'google-key.json'),
    scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
    subject: 'admin@example.com' // google admin email address to impersonate
  });

  await authClient.authorize(); // once authorized, can do whatever you want

  const gmail = google.gmail({
    auth: authClient,
    version: 'v1'
  });

  const response = await gmail.users.messages.list({
    includeSpamTrash: false,
    maxResults: qty,
    q: "",
    userId: userId
  });

  // the data object includes a "messages" array of message data
  return response.data;

}
cpereyra5199 commented 4 years ago

@vettloffah What was the actual fix here? I am running into the same issue and have not been able to come up with a solution.

vettloffah commented 4 years ago

@cpereyra5199 i posted the solution in my last comment. You have to delegate the service to a specific user using JWT authentication

klvenky commented 4 years ago

@vettloffah I would like to know "if you have used a service account for authorization?"

klvenky commented 4 years ago

I have actually tried something similar with GoogleAuth. I didn't understand what went wrong here. I have even tried the approach which @vettloffah has mentioned by replacing the GoogleAuth code. Can someone point me to what's wrong here?


import { google } from "googleapis";
import dotenv from "dotenv";

import path from "path";
dotenv.config();

(async () => {
  const SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"];

  const fileName = process.env.GOOGLE_APPLICATION_CREDENTIALS!;
  console.log({ fileName });
  const keyFile = path.resolve("./",fileName);
  console.log("calling gauth client");
  const gAuth = new google.auth.GoogleAuth({
    keyFile,
    scopes: SCOPES,
  });

  google.options({ auth: gAuth });
  const token = await gAuth.getAccessToken();
  console.log({ token });
  // token is getting generated here.
  const gmail = await google.gmail({ auth: gAuth, version: "v1" });
  const userId = process.env.SERVICE_ACCOUNT! || "me";
  const messages = await gmail.users.messages.list({ userId, token });
  console.log(messages);
})();
`
klvenky commented 4 years ago

I have found another issue with a similar error here I am using a personal gmail account for this. And, I have created a service account for the same. @JustinBeckwith Can you let me know if this is possible or not?

sqrrrl commented 4 years ago

The Gmail API isn't intended to be used with service accounts (other than domain-wide delegation use cases.) You need to be acting as a real user -- either using oauth credentials obtained with user consent, or in the case of a Gsuite domain, using a service account delegating/impersonating a real user.

I'll file a bug to make the error message clearer, but it's likely that the failed precondition is that the service account isn't a valid gmail user.

fhinkel commented 3 years ago

Greetings, we're closing this. Looks like the issue got resolved. Please let us know if the issue needs to be reopened.

martinschnurer commented 2 years ago

The Gmail API isn't intended to be used with service accounts (other than domain-wide delegation use cases.) You need to be acting as a real user -- either using oauth credentials obtained with user consent, or in the case of a Gsuite domain, using a service account delegating/impersonating a real user.

I'll file a bug to make the error message clearer, but it's likely that the failed precondition is that the service account isn't a valid gmail user.

This is ridiculous.

Wolverine971 commented 2 years ago

This is the way

Jerther commented 2 years ago

And here's how to do it in Python

aixi-mfisher commented 1 year ago

So it has to be able to impersonate any user or none at all? Isn't that a pretty significant security risk?

sqrrrl commented 1 year ago

Impersonation is neither required nor encouraged. Use regular OAuth 2.

Most of the conversation has been about wanting to use the gmail API with a service account where the service account is running as it's own identity. That isn't supported as gmail is inherently a product for human users. Service accounts with delegation is an option for some enterprise use cases, and the scope of delegation is users within that org.

There are handful of operations that actually require delegation, and that's mostly for security reasons. Settings that create forwarding rules are particularly sensitive as those forwarding rules persist even after a user has revoked access to an app. Anything guarded by https://www.googleapis.com/auth/gmail.settings.sharing is in that camp.

But the rest of the API no service accounts and no delegation required.