3

I want to interact with the Google's Drive API from a Cloud Function for Firebase. For authentication / authorization, I am currently relying on getClient, which I believe uses the Service Account exposed in the Cloud Function environment:

import { google } from 'googleapis';

// Within the Cloud Function body:
const auth = await google.auth.getClient({
  scopes: [
    'https://www.googleapis.com/auth/drive.file',
  ],
});
const driveAPI = google.drive({ version: 'v3', auth });

// read files, create file etc. using `driveAPI`...

The above approach works, as long as target directories / files list the email address of the service account as an editor.

However, I'd like to interact with the Drive API on behalf of another user (which I control), so that this user becomes (for example) the owner of files being created. How can I achieve this?

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
ErikWittern
  • 1,083
  • 9
  • 13
  • in order to do it on behalf of another user your going to have to configure domain wide delegation to the service account, via google workspace. – Linda Lawton - DaImTo Nov 01 '22 at 16:27
  • @DaImTo Thank you for that pointer! Based on this suggestion, I set up domain-wide delegation in the Google Workspaces Admin UI for the Service Account in question. How would I define the email address to impersonate in `getClient`, though? – ErikWittern Nov 02 '22 at 07:21
  • client.subject = user; <-- should work Are you using javascript or nodejs? – Linda Lawton - DaImTo Nov 02 '22 at 07:37
  • I am using the Google APIs Node.js Client within Cloud Functions for Firebase. My code is written in TypeScript. – ErikWittern Nov 02 '22 at 07:52

2 Answers2

2

I was able to achieve calling the Drive API on behalf of another user thanks to the suggestions made by @DalmTo.

The first step is to configure domain-wide delegation of authority in Google Workspace for the default AppEngine Service Account.

Next, the code in my question can be extended to receive a subject with the email of the user to impersonate via the clientOptions:

import { google } from 'googleapis';

// Within the Cloud Function body:
const auth = await google.auth.getClient({
  scopes: [
    'https://www.googleapis.com/auth/drive.file',
  ],
  clientOptions: {
    subject: 'email@to.impersonate',
  },
  keyFile: './serviceAccountKey.json',
});
const driveAPI = google.drive({ version: 'v3', auth });

// read files, create file etc. using `driveAPI`...

Now, the truly odd thing is that this only works when also passing the service account key via the keyFile option in addition. I.e., relying on the key being automatically populated (as it is in Cloud Functions for Firebase) does NOT work when also trying to impersonate a request. There are ongoing discussions of this bug on GitHub, specifically see this comment.


To make domain-wide delegation work without having to provide the keyFile option (which will likely require you to manage the sensitive key file in some way), another option is to sign a JWT and use it to obtain an Oauth token. The approach is outlined by Google, and I found this SO answer providing a code-example very helpful.

ErikWittern
  • 1,083
  • 9
  • 13
1

To set the user you want to deligate as in your code just add a subject to the client, with user being the email of the user on your workspace domain.

const client = await auth.getClient();
client.subject = user;
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Thanks! I tried setting `client.subject` to the email address of the user to impersonate, but it has no effect: The file created with the client is still owned by the service account's email address. I am using TypeScript and the type checker complaints when setting `client.subject`, as field `subject` does not exist on any of the types returned by `google.auth.getClient`. Maybe I cannot use `getClient` for this use-case? – ErikWittern Nov 02 '22 at 07:56
  • You got me it should work. you could just do a permissions insert to get around it though. But tbh you shouldnt have to. – Linda Lawton - DaImTo Nov 02 '22 at 08:00
  • Not having any success so far. I also tried passing the subject as part of the options for `getClient` like so: `google.auth.getClient({ clientOptions: { subject: 'example@mail.com', }, ...});`. But the file created still lists the original service account. – ErikWittern Nov 02 '22 at 08:34
  • should be something like this https://stackoverflow.com/a/73034970/1841839 sorry i dont have the power of nodejs right now so i cant test it. – Linda Lawton - DaImTo Nov 02 '22 at 08:38
  • Thank you for your pointers! The code you linked does not error, but it does also not lead to the `user` email being set instead of the service account one. Can it be that configuring changes to domain-wide delegation take time to propagate? I just set things up ±1 hour ago. – ErikWittern Nov 02 '22 at 08:59