-
Notifications
You must be signed in to change notification settings - Fork 388
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
User Impersonation with Google Service Account #916
Comments
i am unable to impersonate successfully as well. Error, code 401
const googleService = {};
const authClient = google.auth.fromJSON(serviceJson);
authClient.scopes = [
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/calendar.events',
];
authClient.subject = '[email protected]';
googleService.authClient = authClient;
async function addGuestAndSendEmail(eventId, calendarId, newGuest) {
const {
data: { attendees = [] },
} = await googleService.event.get(eventId, calendarId);
attendees.push({ email: newGuest });
return calendar.events.patch({
calendarId,
eventId,
auth: googleService.authClient,
requestBody: {
sendUpdates: 'all',
attendees,
},
});
} service account where I got service account granted access on google admin Badly need help :(( |
without authClient.subject, i get error code 403
|
also tried const authClient = new google.auth.JWT({
email: serviceJson.client_email,
key: serviceJson.private_key,
scopes: [
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/calendar.events',
'https://www.googleapis.com/auth/cloud-platform',
],
subject: '[email protected]',
});
googleService.authClient = authClient; with error
|
I was able to do impersonation with official python client library, so I suspect there's something wrong with this NodeJS client |
There's an outstanding PR, #779, to add support for impersonation. Perhaps try this as a starting point, and see if the approach would work for you? |
Hey @bcoe thanks for pointing out the PR, I wish to have seen this a few days ago 😄 We've finished the feature in the python client so I think I'll be delayed in testing the PR |
i tried to impersonate a user under the domain but it didn't work. I got error
sample code: const saclient = new JWT(
serviceJson.client_email,
null,
serviceJson.private_key,
scopes,
);
// Use that to impersonate the targetPrincipal
const targetClient = new Impersonated({
sourceClient: saclient,
targetPrincipal: '[email protected]',
lifetime: 30,
delegates: [],
targetScopes: scopes,
});
const authHeaders = await targetClient.getRequestHeaders();
console.log('authHeaders', authHeaders); I guess this impersonation applies only to a service account, not to domain users |
https://stackoverflow.com/a/61571003/3539640
@bcoe Has impersonation been implemented in this package? |
Is this working fine |
maybe this could help
|
@dr-aiuta you save my life!! After hours of searching... Not sure why Google don't put this on docs... |
@bcoe, @dr-aiuta's approach worked for me. Also working for me is the following (Calendar API specific example):
Bear in mind it impersonates a user, not a separate Service Account. I was only able to find this info on SO/here/a couple of blogs. It would be helpful if it were documented if it isn't already. |
Thank you much. |
can this "clientOptions" parameter be put on one of the Quickstarts pages as one of the examples? I spent hours trying to find a solution and I'm so relieved to find this thread. This should be standard in the Directory API docs. |
Hi .. I am trying to use the service account using Google Cloud Functions to access the Workspace Directory API. I am trying to use Application Default Credentials approach. Since the documentation doesn't mention any additional steps to be done in the Google Cloud Console side, I assume that Application Default Credentials (ADC) to the function is automatically passed to the cloud function from Google Cloud's Metadata server. The following code works perfectly in my local, emulated environment where I have set GOOGLE_APPLICATION_CREDENTIALS environment variable to point to the JSON credential file. However when I deploy the function to Google Cloud, I am getting the error
|
I am having the same issue as @increos and would like to replicate the solution provided for Python: https://github.com/GoogleCloudPlatform/professional-services/blob/master/examples/gce-to-adminsdk/main.py |
@jmkrimm My conclusion through trial and error (correctly or incorrectly ) is the ADC strategy works for "newer?" Google Cloud Platform service e.g. Cloud Storage, Secrets Manager etc. But if you are reaching beyond to other (legacy?) Google Products like Workspace then you need other approaches. I got it to work by using using the Secret Manager product. Storing my keys there and reading those in and explicitly using the JWT token to get access to the Google Admin Directory API. I am pasting excerpts of my code below (in typescript) if it helps in anyway :
... ...
and the finally
|
@increos sorry but that solution will not work because I am trying not to create a KEY file at all. When code runs on GCP and the default service account has the necessary authorization, it should just work. Similar to authenticaing to GCP resources like Cloud Storage. The issue is not with the Google API but the nodejs library. Google Workspace API in most cases expects impersonation of a Google Workspace account and the client library does not support this without providing a key file. It is very frustrating for enterprise Google Workspace customers like myself. |
Wow great, thank you so much 😄 , I've been looking for this all day long |
for me the solution with JWT worked: const auth = new google.auth.JWT({
keyFile: 'path-to-service-account.json',
scopes: [
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/calendar.events',
],
subject: '[email protected]',
}) |
THANK YOU! |
Thanks! This solved everything! ❤️ |
OMG THANK YOU SO MUCH |
For those of you who still encounter this problem and hasn't found light, try redownloading json credentials. If you add scope or domain-wide delegation to the service account, it seems the private key will also change, and thus you need to download a new one |
I'm trying too to use the Application Default Credentials to create a JWT token to impersonate the users. This will be very useful to deploy both in production and in test enviroment without a cumbersone json file to every time manage |
Wow, so to this date, the Node.js library has no way of just using the ADC? Instead, we have to explicitly create a key for the Service Account (which is a bad practice as per Google's documentation) @bcoe @danielbankhead can you please shed some light on this. I believe both Python and Java libraries have this functionality already as expressed in this tutorial of yours and it seems crazy that I have to increase security risks at my organisation due to a missing feature... Edit: FYI manually creating a new Testsconst auth = new GoogleAuth({
clientOptions: { // these seem to be ignored altogether
subject: hostEmail,
},
scopes: [ // added just in case, but I've tried without this and same thing
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/calendar.events',
],
});
const targetClient = new Impersonated({
sourceClient: authClient,
targetPrincipal: targetUserToImpersonateWithServiceAccount,
lifetime: 30,
delegates: [],
targetScopes: [
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/calendar.events',
],
});
const authHeaders = await targetClient.getRequestHeaders(); results in a Doing a Frankenstein-worthy workaround: const authClient: { sourceClient: AuthClient } = (await auth.getClient()) as unknown as {
sourceClient: AuthClient;
};
const targetClient = new Impersonated({
sourceClient: authClient.sourceClient,
// ... I get a There's no way to use an impersonated Service Account to impersonate a Workspace user via ADC for Node as far as I can tell, and the maintainers seem not to mind it. I really want to be wrong, but nothing points me to think otherwise. I guess it's either the JWT way or logging in directly as the SA, without impersonation (is that even possible?) :/ |
This comment was marked as off-topic.
This comment was marked as off-topic.
@jars you're doing the opposite of what we're trying to achieve, which we know works ;). This issue is about using a Service Account to impersonate a user, not the other way around. For example, I want to be able to send a calendar invitation or an email on behalf of colleagues in my Domain (with their prior consent, obviously) via Domain-Wide Delegation (DWD). Hope that clears it up! Note: Mind you, this is already possible in other client libraries, but not for the Node one for some reason |
Ahh -- Got it, @BoscoDomingo. I don't want to derail convo. Will search for a more suitable place online to post my findings. Update: I used the Hide function above and found a SO Question to answer instead. |
Can you please direct me how to do this in python client . Also does this method need Domain wide delegation? Its difficult for us to enable domain wide delegation because I dont need to invite users or access their calenders. the only thing I need is to create google meeting where everybody can join |
@akshaychopra5207, Did you ever figure this out? I am also trying to created a google meeting where everyone can join and getting this error in python? |
Just FYI this is still an issue, 4 years later. Python and Java already have this functionality, so it's a matter of translating code unless I'm missing something (I haven't been able to look into the code) |
Hello, I am not sure this is helpful or not for people who like us wants to use WIF with temporary access credentials generated from the client library config file and impersonation with SA for domain wide delegation. We rebuilt the functionality used in this Github Action (https://github.com/google-github-actions/auth) so all credit to that source for figuring out the boilerplate. We reuse this in Lambdas and other types of AWS Services. Full disclaimer I don't consider myself to be a node / typescript coder so don't take the code here for granted. There are probably a lot of things that could be neater import axios from 'axios'; // we need this to build the DWD
import { ExternalAccountClient, OAuth2Client } from 'google-auth-library';
import { SecretService } from './secret'; // deals with fetching secrets from aws secretsmanager
import { SignedJWT } from '../domain/google';
/*
export interface SignedJWT {
kid: string;
signedJwt: string;
}
*/
import { GOOGLE_API_SCOPES } from '../constants'; // Just definition of the API Scopes requested
import { logger } from '../logging'; // This is a lambda powertools configuration
const {
ENVIRONMENT,
GCP_PROJECT_ID,
SERVICE_ACCOUNT_EMAIL
} = process.env;
// userAgent is the default user agent.
const userAgent = `someuser-agent-you-want-to-use`;
export async function generateOauth2Client(secretService: SecretService, impersonationEmail: string): Promise<OAuth2Client> {
const creds = await secretService.getGoogleSecret(); // get SA config for WIF
const auth_client = ExternalAccountClient.fromJSON(creds);
if (!auth_client) { throw Error('no client created') }
auth_client.scopes = ['https://www.googleapis.com/auth/cloud-platform'];
if (!GCP_PROJECT_ID || !SERVICE_ACCOUNT_EMAIL) { throw Error('missing required gcp env vars') }
if (!auth_client) { throw Error('no client created') }
logger.debug("requesting dwd credentials");
const unsignedJwt = buildDomainWideDelegationJWT(
SERVICE_ACCOUNT_EMAIL,
impersonationEmail,
GOOGLE_API_SCOPES,
3600, // 1 hour, longer duration is unsupported with DWD
);
// build dwd request
const url = `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:signJwt`;
const headers = await auth_client.getRequestHeaders();
const body = {
payload: unsignedJwt
}
const signJwtResponse = (await auth_client.request({
url: url,
body: JSON.stringify(body),
headers: headers,
method: 'POST'
})).data as SignedJWT;
if (!signJwtResponse.signedJwt) {
throw new Error("No data in response")
}
const access_token = await generateDomainWideDelegationAccessToken(signJwtResponse.signedJwt);
// We get an OAuth2 access token, we need to convert this into a client to be able to initiate the other google api clients such as admin or gmail
const oauth2Client = new OAuth2Client();
oauth2Client.setCredentials({ access_token: access_token });
return oauth2Client
function buildDomainWideDelegationJWT(
serviceAccount: string,
subject: string | undefined | null,
scopes: Array<string> | undefined | null,
lifetime: number,
): string {
const now = Math.floor(new Date().getTime() / 1000);
const body: Record<string, string | number> = {
iss: serviceAccount,
aud: 'https://oauth2.googleapis.com/token',
iat: now,
exp: now + lifetime,
};
if (subject && subject.trim().length > 0) {
body.sub = subject;
}
if (scopes && scopes.length > 0) {
// Yes, this is a space delimited list.
// Not a typo, the API expects the field to be "scope" (singular).
body.scope = scopes.join(' ');
}
return JSON.stringify(body);
}
/*
We need to send this request with axios (or other http client) because we want to use a JWT (JSON Web Token) for Domain-Wide Delegation of Authority.
The Google Auth Library for Node.js does not provide a built-in method for this specific use case as far as we have found
*/
async function generateDomainWideDelegationAccessToken(
signedJwt: string,
): Promise<string> {
const url = 'https://oauth2.googleapis.com/token';
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': userAgent
};
const body = new URLSearchParams();
body.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
body.append('assertion', signedJwt);
try {
const resp = await axios.post(url, body.toString(), { headers });
if (resp.status < 200 || resp.status > 299) {
throw new Error(`Failed to call ${url}: HTTP ${resp.status}: ${resp.data || '[no body]'}`);
}
logger.debug("Got oauth2 access token")
return resp.data.access_token;
} catch (err) {
throw new Error(`Failed to generate Google Cloud Domain Wide Delegation OAuth 2.0 Access Token: ${err}`);
}
}
} Apart from this the service account needs to have service account token creator permission. You can then start other clients such as admin or gmail clients ie. import { gmail_v1 } from '@googleapis/gmail';
const oauth2Client = await generateOauth2Client(secretService, impersonationEmail);
gmailClient = new gmail_v1.Gmail({ auth: oauth2Client }); |
I want to build in top of @tcvall86 answer, but using the application default credentials, as the original OP requested (and I also needed), so here is the improved version (moved back to vanilla JS): const axios = require("axios"); // we need this to build the DWD
const { GoogleAuth } = require("google-auth-library");
const { OAuth2Client } = require("google-auth-library");
logger = console;
// userAgent is the default user agent.
const userAgent = `someuser-agent-you-want-to-use`;
async function generateOauth2Client(impersonationEmail, gcpProjectId, serviceAccountEmail, googleApiScopes) {
const auth_client = new GoogleAuth({
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
});
if (!auth_client) {
throw Error("no client created");
}
if (!gcpProjectId || !serviceAccountEmail) {
throw Error("missing required gcp env vars");
}
if (!auth_client) {
throw Error("no client created");
}
logger.debug("requesting dwd credentials");
const unsignedJwt = buildDomainWideDelegationJWT(
serviceAccountEmail,
impersonationEmail,
googleApiScopes,
3600 // 1 hour, longer duration is unsupported with DWD
);
// build dwd request
const url = `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccountEmail}:signJwt`;
const headers = await auth_client.getRequestHeaders();
const body = {
payload: unsignedJwt,
};
const signJwtResponse = (
await auth_client.request({
url: url,
body: JSON.stringify(body),
headers: headers,
method: "POST",
})
).data;
if (!signJwtResponse.signedJwt) {
throw new Error("No data in response");
}
const access_token = await generateDomainWideDelegationAccessToken(
signJwtResponse.signedJwt
);
// We get an OAuth2 access token, we need to convert this into a client to be able to initiate the other google api clients such as admin or gmail
const oauth2Client = new OAuth2Client();
oauth2Client.setCredentials({ access_token: access_token });
return oauth2Client;
function buildDomainWideDelegationJWT(
serviceAccount,
subject,
scopes,
lifetime
) {
const now = Math.floor(new Date().getTime() / 1000);
const body = {
iss: serviceAccount,
aud: "https://oauth2.googleapis.com/token",
iat: now,
exp: now + lifetime,
};
if (subject && subject.trim().length > 0) {
body.sub = subject;
}
if (scopes && scopes.length > 0) {
// Yes, this is a space delimited list.
// Not a typo, the API expects the field to be "scope" (singular).
body.scope = scopes.join(" ");
}
return JSON.stringify(body);
}
/*
We need to send this request with axios (or other http client) because we want to use a JWT (JSON Web Token) for Domain-Wide Delegation of Authority.
The Google Auth Library for Node.js does not provide a built-in method for this specific use case as far as we have found
*/
async function generateDomainWideDelegationAccessToken(signedJwt) {
const url = "https://oauth2.googleapis.com/token";
const headers = {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": userAgent,
};
const body = new URLSearchParams();
body.append("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
body.append("assertion", signedJwt);
try {
const resp = await axios.post(url, body.toString(), { headers });
if (resp.status < 200 || resp.status > 299) {
throw new Error(
`Failed to call ${url}: HTTP ${resp.status}: ${
resp.data || "[no body]"
}`
);
}
logger.debug("Got oauth2 access token");
return resp.data.access_token;
} catch (err) {
throw new Error(
`Failed to generate Google Cloud Domain Wide Delegation OAuth 2.0 Access Token: ${err}`
);
}
}
}
module.exports = {
generateOauth2Client
}; And this is the way that it is used: [...]
const { generateOauth2Client } = require("./impersonation.js");
[...]
const auth = await generateOauth2Client(
"<USE-OWN-IMPERSONATED-USER>",
"<USE-YOUR-OWN-PROJECT>",
"<USE-YOUR-OWN-IMPERSONATING-SA",
[
"https://www.googleapis.com/auth/drive.readonly",
]
);
const service = google.drive({ version: "v3", auth });
resAbout = await service.about.get({
fields: "*",
});
console.log(resAbout.data.user); The result of said code is printing the information about the impersonated user, meaning that we have managed to really impersonate it! |
@lopezvit Nice one! I will give it a go whenever I have time for it, and let you know if it worked for me too. Can't promise it'll be soon though (it's for work and we've more pressing matters atm) |
In case anyone else is interested in the TypeScript version of @tcvall86 and @lopezvit snippets: import { GoogleAuth } from 'google-auth-library';
const GOOGLE_OAUTH2_TOKEN_API_URL = 'https://oauth2.googleapis.com/token';
/**
* This function generates an OAuth2 Access Token with scopes obtained via domain-wide delegation
* without requiring a JSON key file from a Service Account.
*
* Resources:
* - https://github.com/googleapis/google-auth-library-nodejs/issues/916
* - https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys?hl=es-419#domain-wide-delegation
*
* @param impersonationEmail Email address of the Google account that has granted domain-wide scopes to the service account
* @param serviceAccountEmail Service account which received the scopes using domain-wide delegation
* @param googleApiScopes Scopes to request for the generated Access Token
* @param lifetime Lifetime, in seconds, of the generated access token. It can't be greater than 1h
* @returns the generated access token
*/
export async function getDomainWideDelegationAccessToken(
impersonationEmail: string,
serviceAccountEmail: string,
googleApiScopes: string[],
lifetime: number,
): Promise<string> {
if (!impersonationEmail) {
throw Error('impersonationEmail is required');
}
if (!serviceAccountEmail) {
throw Error('serviceAccountEmail is required');
}
if (lifetime > 3600) {
throw Error('lifetime cannot be greater than 3600 seconds (1 hour)');
}
// Build client using Application Default Credentials
const auth_client = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
});
// Build JWT token for domain-wide delegation
const unsignedJwt = buildDomainWideDelegationJWT(serviceAccountEmail, impersonationEmail, googleApiScopes, lifetime);
// Sign JWT token using a system-managed private key of the given service account
const signJwtResponse = (
await auth_client.request({
url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccountEmail}:signJwt`,
body: JSON.stringify({
payload: unsignedJwt,
}),
headers: await auth_client.getRequestHeaders(),
method: 'POST',
})
).data;
if (!signJwtResponse.signedJwt) {
throw new Error('Failed to sign JWT token using the Service Account key');
}
return await generateDomainWideDelegationAccessToken(signJwtResponse.signedJwt);
}
/**
* Builds the payload to request a JWT token using domain-wide delegation.
* See: https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys?hl=es-419#domain-wide-delegation
*/
function buildDomainWideDelegationJWT(
serviceAccount: string,
subject: string,
scopes: string[],
lifetime: number,
): string {
const now = Math.floor(new Date().getTime() / 1000);
const body: Record<string, string | number | undefined> = {
iss: serviceAccount,
aud: GOOGLE_OAUTH2_TOKEN_API_URL,
iat: now,
exp: now + lifetime,
sub: subject ?? undefined,
// Yes, this is a space delimited list.
// Not a typo, the API expects the field to be "scope" (singular).
scope: scopes && scopes.length > 0 ? scopes.join(' ') : undefined,
};
return JSON.stringify(body);
}
/**
* We need to send this request using an alternative http client because we want to use a JWT (JSON Web Token) for Domain-Wide Delegation of Authority.
* The Google Auth Library for Node.js does not provide a built-in method for this specific use case.
* See: https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys?hl=es-419#domain-wide-delegation
*/
async function generateDomainWideDelegationAccessToken(signedJwt: string): Promise<string> {
const url = GOOGLE_OAUTH2_TOKEN_API_URL;
const headers = {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
};
const body = new URLSearchParams();
body.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
body.append('assertion', signedJwt);
try {
const resp = await fetch(url, {
method: 'POST',
body: body.toString(),
headers,
});
if (resp.status < 200 || resp.status > 299) {
throw new Error(`Failed to call ${url}: HTTP ${resp.status}: ${await resp.text()}`);
}
const data = (await resp.json()) as { access_token: string };
return data.access_token;
} catch (err) {
throw new Error(`Failed to generate Google Cloud Domain Wide Delegation OAuth 2.0 Access Token: ${err}`);
}
} This can be used as follows: // Generate access token using Domain-wide delegation
const accessToken = await getDomainWideDelegationAccessToken(
'[email protected]',
'[email protected]',
['scope1', 'scope2'], // Requested scopes
15 * 60, // Lifetime of the access token, it cannot be greater than 1h
)
const oauth2Client = new OAuth2Client();
oauth2Client.setCredentials({ access_token: accessToken }); |
I would also really like this feature. Here is what I am currently using - I adjusted the above code a little bit to make use of the import { IAMCredentialsClient } from '@google-cloud/iam-credentials'
const GOOGLE_OAUTH2_TOKEN_API_URL = 'https://oauth2.googleapis.com/token';
const buildUnsignedJwt = (serviceAccountEmail: string, impersonatedWorkspaceUserEmail: string, scopes: string[]): string => {
const now = Math.floor(new Date().getTime() / 1000);
const body: Record<string, string | number | undefined> = {
iss: serviceAccountEmail,
aud: GOOGLE_OAUTH2_TOKEN_API_URL,
iat: now,
exp: now + 3600,
sub: impersonatedWorkspaceUserEmail,
scope: scopes.join(' '),
}
return JSON.stringify(body)
}
const generateDomainWideDelegationAccessToken = async (signedJwt: string): Promise<string> => {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
};
const body = new URLSearchParams();
body.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
body.append('assertion', signedJwt);
try {
const resp = await fetch(GOOGLE_OAUTH2_TOKEN_API_URL, {
method: 'POST',
body: body.toString(),
headers,
});
if (resp.status < 200 || resp.status > 299) {
throw new Error(`Failed to call ${GOOGLE_OAUTH2_TOKEN_API_URL}: HTTP ${resp.status}: ${await resp.text()}`);
}
const data = (await resp.json()) as { access_token: string };
return data.access_token;
} catch (err) {
throw new Error(`Failed to generate Google Cloud Domain Wide Delegation OAuth 2.0 Access Token: ${err}`);
}
}
export const getDomainWideDelegationAccessToken = async (impersonatedWorkspaceUserEmail: string, serviceAccountEmail: string, scopes: string[]): Promise<string> => {
if (!impersonatedWorkspaceUserEmail) throw Error('impersonationEmail is required');
if (!serviceAccountEmail) throw Error('serviceAccountEmail is required');
if (scopes.length === 0) throw Error('No scopes were provided');
// Build unsigned JWT
const unsignedJwt = buildUnsignedJwt(serviceAccountEmail, impersonatedWorkspaceUserEmail, scopes);
// Sign JWT token using a system-managed private key of the given service account
const [signedJwtResponse] = await new IAMCredentialsClient().signJwt({
name: `projects/-/serviceAccounts/${serviceAccountEmail}`,
payload: unsignedJwt,
})
const { signedJwt } = signedJwtResponse
if (!signedJwt) throw new Error('Failed to sign JWT token using the Service Account key');
return await generateDomainWideDelegationAccessToken(signedJwt);
} |
Thanks for your code contributions @tcvall86 @lopezvit @alitto @antnat96 ! So helpful! For anyone else trying out one of those solutions, make sure to add the Service Account Token Creator role to your service account in IAM. (I needed it with @alitto 's version.) It's so annoying this is still a limitation in the library, but this workaround is great! |
Let me add that // Sign JWT token using a system-managed private key of the given service account
await google.iamcredentials('v1').projects.serviceAccounts.signJwt({
name: `projects/-/serviceAccounts/${serviceAccountEmail}`,
requestBody: {
// Build JWT token for domain-wide delegation
payload: unsignedJWT,
},
auth,
}) |
I am not sure if this helps others but I was able to provide a service account using export function getGoogleAuth(serviceAccount: string): Auth.GoogleAuth {
const SCOPES = [...];
const impersonationEmail = "[email protected]";
return new google.auth.GoogleAuth({
scopes: SCOPES,
clientOptions: {subject: impersonationEmail},
credentials: JSON.parse(serviceAccount),
});
} |
Thank god i found this thread because for me it was totally unclear how to deal with the Problem is, you see pretty good tutotials on the web for other programming languages, like this one for C# (https://medium.com/iceapple-tech-talks/integration-with-google-calendar-api-using-service-account-1471e6e102c8) and then you realize that the c# client obviously behaves differently than my target language JS/TS and the node lib. What i still dont get, that while i cant add attendees to a calendar event without clientOptions->subject, the mentioned subject has nothing to do with my attendees emails or the service user i am using. I just added one of my admin email address from my workspace users. I dont get the idea of "subject" at all. I always thought the service account is the one which does the calls. |
Error creating event: Service accounts cannot invite attendees without Domain-Wide Delegation of Authority. |
Hi. I'm trying to implement user impersonation with a google service account and have been having problems for a while. After adding the user to be impersonated as the subject, a token gets created but when calling an API like Google Calendar I get a 401 Invalid Credentials error as if the token that was just created has expired or is invalid.
Do you have any samples of user impersonation? I don't see any in the samples or using Node in Google's documentation. Here is their documentation on the subject: https://developers.google.com/identity/protocols/oauth2/service-account#delegate-domain-wide-authority
Thanks a lot.
The text was updated successfully, but these errors were encountered: