How to securely use Google API service account credentials in a public repo
Tags: javascript, security, node, googlecloud
Published: 2022-02-18 23:28:04

**TLDR:** You can securely use Google API service account credentials in a public repo by storing sensitive parts of the credentials file in environment variables and generating a credentials file in a pre-build script.

---

I'm building a Node.js app that needs to write and read data from a private Google Sheet. While Google provides multiple ways to authorize requests to the Google Sheets API (see [documentation](https://developers.google.com/sheets/api/guides/authorizing)), the only viable option for my use case is to use service account credentials. According to the [Google APIs Node.js client documentation](https://github.com/googleapis/google-api-nodejs-client#service-account-credentials), the only way to use service account credentials is to download a credentials file from the Google Developer Console and store it in your project. You then reference the credentials file in your code like this:

```javascript
import { google } from 'googleapis';

const auth = new google.auth.GoogleAuth({
  keyFile: 'path/to/credentials.json',
  scopes: 'https://www.googleapis.com/auth/spreadsheets',
});
```

> **Note:** In addition to creating a service account, you will need to explicitly give the service account edit rights to your Google Sheet, just as if you were giving edit rights to a friend.

So the docs direct us to store the credentials file in our project, yet the credentials file contains sensitive information, so we don't want to check it into source control.

How do we get around this?

The typical approach for this kind of scenario is to store the credentials in environment variables. But the Google client's API expects the credentials to be in a file, not in environment variables. So for this case, we need to take an extra step: we need to generate the credentials file at build time, referencing our environment variables. 

## Extracting sensitive info from the credentials file

The credentials file I downloaded from the Google Developer Console looked something like this:

```json
{
  "type": "service_account",
  "project_id": "my-project",
  "private_key_id": "aonuUqnocuh234oqlkr",
  "private_key": "super-long-string-qsuOHKRU035Okr",
  "client_email": "example@mail.com",
  "client_id": "Ouhr13QurlohUk03",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/{project-specific-stuff}.iam.gserviceaccount.com"
}
```

I decided to extract the values for the following keys into environment variables:

|Google Credentials Key|Environment Variable|
|---|---|
|`private_key_id`|`GOOGLE_PRIVATE_KEY_ID`|
|`private_key`|`GOOGLE_PRIVATE_KEY`|
|`client_email`|`GOOGLE_CLIENT_EMAIL`|
|`client_id`|`GOOGLE_CLIENT_ID`|
|`client_x509_cert_url`|`GOOGLE_CLIENT_X509_CERT_URL`|

Here is what my `.env` file looks like:

**.env**
```env
GOOGLE_PRIVATE_KEY_ID="my-private-key-id"
GOOGLE_PRIVATE_KEY="my-private-key"
GOOGLE_CLIENT_EMAIL="my-client-email"
GOOGLE_CLIENT_ID="my-client-id"
GOOGLE_CLIENT_X509_CERT_URL="my-client-x509-cert-url"
```

> **Note:** Make sure you add `.env` to your `.gitignore` to avoid checking this file into source control!

I'm using a `.env` file for local development, but when I deploy the app, I will enter the environment variables in the hosts UI or CLI (e.g., Netlify, Heroku, etc.).

## Generating our credentials file

Great! We've successfully stored our secrets from the credentials file in environment variables. Now we need to write the script that will use them to generate the JSON file at build time. 

**generate-google-api-credentials.js**
```javascript
const fs = require('fs');
// Load variables from .env into process.env
require('dotenv').config();

const credentials = {
  type: 'service_account',
  project_id: 'my-project',
  private_key_id: process.env.GOOGLE_PRIVATE_KEY_ID,
  private_key: process.env.GOOGLE_PRIVATE_KEY,
  client_email: process.env.GOOGLE_CLIENT_EMAIL,
  client_id: process.env.GOOGLE_CLIENT_ID,
  auth_uri: 'https://accounts.google.com/o/oauth2/auth',
  token_uri: 'https://oauth2.googleapis.com/token',
  auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs',
  client_x509_cert_url: process.env.GOOGLE_CLIENT_X509_CERT_URL,
};

fs.writeFileSync(
  'google-api-credentials.json',
  JSON.stringify(credentials, null, 2)
);
```
> **Note:** Make sure to reference the generated credentials file (i.e., `google-api-credentials.json`) in your `.gitignore`!

## Running our script at build time

Now that we have a script to generate our credentials, we can run it as needed from our `package.json`:

**package.json**
```json
{
  "scripts": {
    "prebuild": "node generate-google-api-credentials.js"
  }
}
```

And now we can use our generate key file to authorize the Google APIs Node.js client to read and write from our private Google Sheet!

```javascript
import { google } from 'googleapis';

const auth = new google.auth.GoogleAuth({
  keyFile: 'google-api-credentials.json',
  scopes: 'https://www.googleapis.com/auth/spreadsheets',
});
```

---

In summary, to use Google API service account credentials securely in a public repo, you need to:

1. **Download** the credentials file from the Google Developer Console
2. **Extract** the sensitive values from the file into environment variables
3. **Generate** the credentials file at build time using the environment variables