user3621334
user3621334

Reputation: 153

How can I use Google default credentials on Heroku without the JSON file?

I'm looking to deploy a Node app to Heroku, and the key challenge I'm running into has to do with the Google default authorization workflow for Node. By default, Google looks for a JSON file with secret keys, with GOOGLE_APPLICATION_CREDENTIALS as the environmental variable name that points to the path of this JSON file. That is fine for local development, but in production I naturally do not want to commit this sensitive JSON file to source. Heroku allows you to create environmental variables, but each variable is individual. Somehow I need to break up this JSON file into individual variables, but I don't know what to call them for Google to recognize them.

There is a similar thread on this for Ruby, but the equivalent does not work in Node.

Upvotes: 7

Views: 4673

Answers (5)

JonW
JonW

Reputation: 41

I was just dealing with the same issue and couldn't find a solution online that I could get to work, so I made my own. I know this is an old issue, but wanted to share, so others might find it down the road.

To solve this problem, I used Node JS to programmatically write the file to the host after deployment by accessing the secret key values in my environment variables.

First, I created an object to behave as a JSON template in my JS code with the correct key names and used my environment variables to get the key values in that object. With that template in place, I was almost ready to have Node write the file to the host,, but I had one issue to solve.

I discovered from another post that JSON.stringify() will modify any backslash ('') that comes from an environment variable into two backslashes ('\') (it will not make this change when a backslash comes from a string in a JavaScript object). This was a problem for me because my private key had 28 backslashes. To correct this, as documented in the link above, I retrieved the value of the environment variable and updated the object before passing the object through JSON.stringify().

With that issue solved, I was able to write the file to the host and successfully deploy my app without exposing my JSON file on my public repository.

const fs = require('fs');

async function createJSONFile() {

  //Replace '\' in environment variable .env file before JSON.stringify() 
  //so that stringify does not turn it into '\\'
  //https://stackoverflow.com/a/36439803/13604562
  jsonFile.private_key = process.env.GCS_JSON_private_key.replace(/\\n/g, '\n');

  let data = JSON.stringify(jsonFile);
  //CHECK IF JSON KEYFILE FOR GCS EXISTS. IF NOT, CREATES FILE
  if (!fs.existsSync(`./${process.env.GCS_KEYFILE}`)) {
    await fs.writeFile(`./${process.env.GCS_KEYFILE}`, data, function (err) {
      if (err) {
        return res.status(400).json(err);
      }
    });
  }
}

//JSON file template
let jsonFile = {
  type: `${process.env.GCS_JSON_type}`,
  project_id: `${process.env.GCS_JSON_project_id}`,
  private_key_id: `${process.env.GCS_JSON_private_key_id}`,
  private_key: `${process.env.GCS_JSON_private_key}`,
  client_email: `${process.env.GCS_JSON_client_email}`,
  client_id: `${process.env.GCS_JSON_client_id}`,
  auth_uri: `${process.env.GCS_JSON_auth_uri}`,
  token_uri: `${process.env.GCS_JSON_token_uri}`,
  auth_provider_x509_cert_url: `${process.env.GCS_JSON_auth_provider_x509_cert_url}`,
  client_x509_cert_url: `${process.env.GCS_JSON_client_x509_cert_url}`,
};

createJSONFile();

Upvotes: 1

A_jain2310
A_jain2310

Reputation: 37

Store the private key i heroku without the double quotes , your solution will work.

Upvotes: -2

user3113645
user3113645

Reputation: 111

When using the google translation api, I ran into the same issue. I was unable to reference the whole JSON file, so I created two env variables in Heroku and referenced them in a credentials object. You can not have them stand alone. The .replace for the private key is an important detail. You should paste in that full key as is on Heroku.

const Translate = require('@google-cloud/translate');
const projectId = 'your project id here';

const translate = new Translate({
  projectId: projectId,
  credentials: {
    private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n'),
    client_email: process.env.GOOGLE_CLIENT_EMAIL
  }
});

Upvotes: 11

Mark
Mark

Reputation: 1347

The getApplicationDefault method is really just a convenience factory for finding the right client. You can actually construct your client directly, passing in the parameters read from environmental variables defined in Heroku.

Take this example which I used recently with a Heroku deployment:

const GoogleAuth = require('google-auth-library');

function authorize() {
    return new Promise(resolve => {
        const authFactory = new GoogleAuth();
        const jwtClient = new authFactory.JWT(
            process.env.GOOGLE_CLIENT_EMAIL, // defined in Heroku
            null,
            process.env.GOOGLE_PRIVATE_KEY, // defined in Heroku
            ['https://www.googleapis.com/auth/calendar']
        );

        jwtClient.authorize(() => resolve(jwtClient));
    });
}

Upvotes: 3

user3621334
user3621334

Reputation: 153

thanks! i also found another solution in case it helps others:

// Authenticating on a global basis. var projectId = process.env.GCLOUD_PROJECT; // E.g. 'grape-spaceship-123'

var gcloud = require('google-cloud')({ projectId: projectId,

credentials: require('./path/to/keyfile.json')

});

this way you can deconstruct the entire json key provided by auth

Upvotes: 1

Related Questions