9
votes

I have an HTTP-triggered function running on Google Cloud Functions, which uses require('googleapis').sheets('v4') to write data into a docs spreadsheet.

For local development I added an account via the Service Accounts section of their developer console. I downloaded the token file (dev-key.json below) and used it to authenticate my requests to the Sheets API as follows:

var API_ACCT = require("./dev-key.json");
let apiClient = new google.auth.JWT(
  API_ACCT.client_email, null, API_ACCT.private_key,
  ['https://www.googleapis.com/auth/spreadsheets']
);

exports.myFunc = function (req, res) {
  var newRows = extract_rows_from_my_client_app_request(req);
  sheets.spreadsheets.values.append({
    auth: apiClient,
    // ...
    resource: { values:newRows }
  }, function (e) {
    if (e) res.status(500).json({err:"Sheets API is unhappy"});
    else res.status(201).json({ok:true})
  });
};

After I shared my spreadsheet with my service account's "email address" e.g. [email protected] — it worked!

However, as I go to deploy this to the Google Cloud Functions service, I'm wondering if there's a better way to handle credentials? Can my code authenticate itself automatically without needing to bundle a JWT key file with the deployment?

I noticed that there is a FUNCTION_IDENTITY=foobar-bazbuzz-123456@appspot.gserviceaccount.com environment variable set when my function runs, but I do not know how to use this in the auth value to my googleapis call. The code for google.auth.getApplicationDefault does not use that.

Is it considered okay practice to upload a private JWT token along with my GCF code? Or should I somehow be using the metadata server for that? Or is there a built-in way that Cloud Functions already can authenticate themselves to other Google APIs?

2

2 Answers

4
votes

It's common to bundle credentials with a function deployment. Just don't check them into your source control. Cloud Functions for Firebase samples do this where needed. For example, creating a signed URL from Cloud Storage requires admin credentials, and this sample illustrates saving that credential to a file to be deployed with the functions.

2
votes

I'm wondering if there's a better way to handle credentials? Can my code authenticate itself automatically without needing to bundle a JWT key file with the deployment?

Yes. You can use 'Application Default Credentials', instead of how you've done it, but you don't use the function getApplicationDefault() as it has been deprecated since this Q was posted.

The link above shows how to make a simple call using the google.auth.getClient API, providing the desired scope, and have it decide the credential type needed automatically. On cloud functions this will be a 'Compute' object, as defined in the google-auth-library.

These docs say it well here...

After you set up a service account, ADC can implicitly find your credentials without any need to change your code, as described in the section above.

Where ADC is Application Default Credentials.

Note that, for Cloud Functions, you use the App Engine service account: [email protected], as documented here. That is the one you found via the FUNCTION_IDENTITY env var - this rather tripped me up.

The final step is to make sure that the service account has the required access as you did with your spreadsheet.