Nodejs-dialogflow: Authentication using service account (without using file)

Created on 30 Jan 2020  Â·  3Comments  Â·  Source: googleapis/nodejs-dialogflow

Hello,

I'm currently migrate API from v1 to v2, one of the big changes is the authentication flow. In v1 I using a secret key, passed through Authorization header during POST request. Value is encrypted with a strong encryption system (AWS KMS) and decrypted before being used.
Now, if I understand well, I need to use a JSON file.. I'm in a stateless environment so I haven't access to file system (and I don't want to save it to disk without encryption for obvious security reasons).

There is a method to pass credentials in a different way? Respect using an environment variable (GOOGLE_APPLICATION_CREDENTIALS) to point to a file on disk, for example a secret/access keys pair. An option in class constructor would be preferable instead of forcing to use an environment variable, maybe I can't even use them for example in Lambda@Edge ecosystem.

Being that file a simple JSON file it should not be so complicated to accept that values as javascript object, something like this:

const sessionClient = new dialogflow.SessionsClient({
  "type": "service_account",
  "project_id": "xxxxxxxxxx
  "private_key_id": "xxxxxxxxxx",
  "private_key": "-----BEGIN PRIVATE KEY-----\n....\n-----END PRIVATE KEY-----\n",
  "client_email": "[email protected]",
  "client_id": "xxxxxxxxxxxxxxxxxxxxxxx",
});

this will let developer to choice how to store values (environment variable/hashicorp vault/aws secret manager..etc..etc..) and most importantly, encrypted.

PS: using files to store configurations is an anti-pattern for 12 factor applications, this require to include that file under-version control (terrible) or creating it during build pipeline (this generate an non-agnostic artifact and is a bad practice IMHO) and cannot be tested locally.

question dialogflow

Most helpful comment

You should be able to pass the contents of the .json file to the SessionsClient constructor on the credentials property like so:

const sessionClient = new dialogflow.SessionsClient({ credentials: { /* the json */ } })

For reference, this is the documentation for the constructor of the SessionsClient:

    /**
     * Construct an instance of SessionsClient.
     *
     * @param {object} [options] - The configuration object. See the subsequent
     *   parameters for more details.
     * @param {object} [options.credentials] - Credentials object.
     * @param {string} [options.credentials.client_email]
     * @param {string} [options.credentials.private_key]
     * @param {string} [options.email] - Account email address. Required when
     *     using a .pem or .p12 keyFilename.
     * @param {string} [options.keyFilename] - Full path to the a .json, .pem, or
     *     .p12 key downloaded from the Google Developers Console. If you provide
     *     a path to a JSON file, the projectId option below is not necessary.
     *     NOTE: .pem and .p12 require you to specify options.email as well.
     * @param {number} [options.port] - The port on which to connect to
     *     the remote host.
     * @param {string} [options.projectId] - The project ID from the Google
     *     Developer's Console, e.g. 'grape-spaceship-123'. We will also check
     *     the environment variable GCLOUD_PROJECT for your project ID. If your
     *     app is running in an environment which supports
     *     {@link https://developers.google.com/identity/protocols/application-default-credentials Application Default Credentials},
     *     your project ID will be detected automatically.
     * @param {function} [options.promise] - Custom promise module to use instead
     *     of native Promises.
     * @param {string} [options.apiEndpoint] - The domain name of the
     *     API remote host.
     */
    constructor(opts?: ClientOptions);

All 3 comments

You should be able to pass the contents of the .json file to the SessionsClient constructor on the credentials property like so:

const sessionClient = new dialogflow.SessionsClient({ credentials: { /* the json */ } })

For reference, this is the documentation for the constructor of the SessionsClient:

    /**
     * Construct an instance of SessionsClient.
     *
     * @param {object} [options] - The configuration object. See the subsequent
     *   parameters for more details.
     * @param {object} [options.credentials] - Credentials object.
     * @param {string} [options.credentials.client_email]
     * @param {string} [options.credentials.private_key]
     * @param {string} [options.email] - Account email address. Required when
     *     using a .pem or .p12 keyFilename.
     * @param {string} [options.keyFilename] - Full path to the a .json, .pem, or
     *     .p12 key downloaded from the Google Developers Console. If you provide
     *     a path to a JSON file, the projectId option below is not necessary.
     *     NOTE: .pem and .p12 require you to specify options.email as well.
     * @param {number} [options.port] - The port on which to connect to
     *     the remote host.
     * @param {string} [options.projectId] - The project ID from the Google
     *     Developer's Console, e.g. 'grape-spaceship-123'. We will also check
     *     the environment variable GCLOUD_PROJECT for your project ID. If your
     *     app is running in an environment which supports
     *     {@link https://developers.google.com/identity/protocols/application-default-credentials Application Default Credentials},
     *     your project ID will be detected automatically.
     * @param {function} [options.promise] - Custom promise module to use instead
     *     of native Promises.
     * @param {string} [options.apiEndpoint] - The domain name of the
     *     API remote host.
     */
    constructor(opts?: ClientOptions);

Hi @OlenDavis,

I try to retrieve the encrypted file content and passing to credentials parameters:

// retrieve file content from AWS Secret Manager
const secretResponse = await secretsManager.getSecretValue({
    SecretId: event.SecretId
}).promise()
// parse response
const parameters = {
    credentials: JSON.parse(secretResponse.SecretString) 
}
// log for debug purpose (just to show you the correct parsed file)
console.log(parameters)
// set credentials parameters
const sessionClient = new dialogflow.SessionsClient(parameters)

file content is correctly formatted as the console.log show:

{ credentials:
   { type: 'service_account',
     project_id: 'myproject-xxxx,
     private_key_id: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
     private_key:
      '-----BEGIN PRIVATE KEY-----\n
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
\n-----END PRIVATE KEY-----\n',
     client_email: '[email protected]',
     client_id: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx',
     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/myproject-xxxxxxxxxx.iam.gserviceaccount.com' } }

but client raise an error:

{
    "errorType": "Error",
    "errorMessage": "Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.",
    "stack": [
        "Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.",
        "    at GoogleAuth.getApplicationDefaultAsync (/var/task/node_modules/google-auth-library/build/src/auth/googleauth.js:160:19)",
        "    at process._tickCallback (internal/process/next_tick.js:68:7)"
    ]
}

seems credentials are not correctly loaded.


Using dialogflow with this version:

$ npm list | grep "dialogflow"
├─┬ [email protected]

on Node 10.x

This issue might no longer be relevant due to its age. Feel free to re-open.

Was this page helpful?
0 / 5 - 0 ratings