DEV Community

Wilson Parson
Wilson Parson

Posted on • Edited on

How to securely use Google API service account credentials in a public repo

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), the only viable option for my use case is to use service account credentials. According to the Google APIs Node.js client documentation, 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:

import { google } from 'googleapis';

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

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:

{
  "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"
}
Enter fullscreen mode Exit fullscreen mode

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

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"
Enter fullscreen mode Exit fullscreen mode

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

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)
);
Enter fullscreen mode Exit fullscreen mode

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

{
  "scripts": {
    "prebuild": "node generate-google-api-credentials.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

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!

import { google } from 'googleapis';

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

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

Top comments (0)